Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support of module types #1169

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,75 @@
}
}

/// Macro for boilerplate of [`ObjectSubclass`] implementations that are
/// registered as dynamic types.
///
/// An object subclass must be explicitly registered as a dynamic type when the
/// system loads its implementation (see [`TypePlugin`] and [`TypeModule`].
/// Therefore, whereas an object subclass can be registered only once as a
/// static type, it can be registered several times as a dynamic type.
///
/// An object subclass registered as a dynamic type is never unregistered. The
/// system calls [`TypePluginExt::unuse`] to unload its implementation. If the
/// [`TypePlugin`] subclass is a [`TypeModule`], the object subclass registered
/// as a dynamic type is marked as unloaded and must be registered again when
/// the module is reloaded.
///
/// This macro provides two behaviors when registering an object subclass as a
/// dynamic type:
///
/// By default an object subclass is registered as a dynamic type when the
/// system loads its implementation (e.g. when the module is loaded):
/// ```ignore
/// #[glib::dynamic_object_subclass]
/// impl ObjectSubclass for MyType { ... }
/// ```
///
/// Optionally setting the macro attribute `lazy_registration` to `true`
/// postpones registration on the first use (when `type_()` is called for the
/// first time), similarly to the [`macro@object_subclass`]
/// macro:
/// ```ignore
/// #[glib::dynamic_object_subclass(lazy_registration = true)]
/// impl ObjectSubclass for MyType { ... }
/// ```
///
/// By default an object subclass is considered to be registered as a dynamic
/// type within a [`TypeModule`] subclass. Optionally setting the macro
/// attribute `plugin_type` allows to register an object subclass as a dynamic
/// type within a given [`TypePlugin`] subclass:
/// ```ignore
/// #[glib::dynamic_object_subclass(plugin_type = MyPlugin)]
/// impl ObjectSubclass for MyType { ... }
/// ```
///
/// [`ObjectSubclass`]: ../glib/subclass/types/trait.ObjectSubclass.html
/// [`TypePlugin`]: ../glib/gobject/type_plugin/struct.TypePlugin.html
/// [`TypeModule`]: ../glib/gobject/type_module/struct.TypeModule.html
/// [`TypePluginExt::unuse`]: ../glib/gobject/type_plugin/trait.TypePluginExt.html#method.unuse
#[proc_macro_attribute]
#[proc_macro_error]
pub fn dynamic_object_subclass(attr: TokenStream, item: TokenStream) -> TokenStream {
use proc_macro_error::abort_call_site;
let attrs = match syn::parse::Parser::parse(
syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated,
attr,
) {
Ok(attrs)
if attrs
.iter()
.all(|attr| matches!(attr, syn::Expr::Assign(_))) =>
{
attrs
}
_ => abort_call_site!(object_subclass_attribute::WRONG_EXPRESSION_MSG),
};
match syn::parse::<syn::ItemImpl>(item) {
Ok(input) => object_subclass_attribute::impl_dynamic_object_subclass(&attrs, &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 +739,77 @@
}
}

/// Macro for boilerplate of [`ObjectInterface`] implementations that are
/// registered as dynamic types.
///
/// An object interface must be explicitly registeredas a dynamic type when the
/// system loads its implementation (see [`TypePlugin`] and [`TypeModule`].
/// Therefore, whereas an object interface can be registered only once as a
/// static type, it can be registered several times as a dynamic type.
///
/// An object interface registered as a dynamic type is never unregistered. The
/// system calls [`TypePluginExt::unuse`] to unload its implementation. If the
/// [`TypePlugin`] subclass is a [`TypeModule`], the object interface
/// registered as a dynamic type is marked as unloaded and must be registered
/// again when the module is reloaded.
///
/// This macro provides two behaviors when registering an object interface as a
/// dynamic type:
///
/// By default an object interface is registered as a dynamic type when the
/// system loads its implementation (e.g. when the module is loaded):
/// ```ignore
/// #[glib::dynamic_object_interface]
/// unsafe impl ObjectInterface for MyInterface { ... }
/// ```
///
/// Optionally setting the macro attribute `lazy_registration` to `true`
/// postpones registration on the first use (when `type_()` is called for the
/// first time), similarly to the [`macro@object_subclass`]
/// [`macro@object_interface`] macro.
/// ```ignore
/// #[glib::dynamic_object_interface(lazy_registration = true)]
/// unsafe impl ObjectInterface for MyInterface { ... }
/// ```
///
/// By default an object interface is considered to be registered as a dynamic
/// type within a [`TypeModule`] subclass. Optionally setting the macro
/// attribute `plugin_type` allows to register an object interface as a dynamic
/// type within a given [`TypePlugin`] subclass:
/// ```ignore
/// #[glib::dynamic_object_interface(plugin_type = MyPlugin)]
/// unsafe impl ObjectInterface for MyInterface { ... }
/// ```
///
/// [`ObjectInterface`]: ../glib/subclass/interface/trait.ObjectInterface.html
/// [`TypePlugin`]: ../glib/gobject/type_plugin/struct.TypePlugin.html
/// [`TypeModule`]: ../glib/gobject/type_module/struct.TypeModule.html
/// [`TypePluginExt::unuse`]: ../glib/gobject/type_plugin/trait.TypePluginExt.html#method.unuse
#[proc_macro_attribute]
#[proc_macro_error]
pub fn dynamic_object_interface(attr: TokenStream, item: TokenStream) -> TokenStream {
use proc_macro_error::abort_call_site;
let attrs = match syn::parse::Parser::parse(
syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated,
attr,
) {
Ok(attrs)
if attrs
.iter()
.all(|attr| matches!(attr, syn::Expr::Assign(_))) =>
{
attrs
}
_ => abort_call_site!(object_interface_attribute::WRONG_EXPRESSION_MSG),
};
match syn::parse::<syn::ItemImpl>(item) {
Ok(input) => {
object_interface_attribute::impl_dynamic_object_interface(&attrs, &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 Expand Up @@ -883,7 +1023,7 @@
///
/// ## Using Rust keywords as property names
/// You might hit a roadblock when declaring properties with this macro because you want to use a name that happens to be a Rust keyword. This may happen with names like `loop`, which is a pretty common name when creating things like animation handlers.
/// To use those names, you can make use of the raw identifier feature of Rust. Simply prefix the identifier name with `r#` in the struct declaration. Internally, those `r#`s are stripped so you can use its expected name in [`ObjectExt::property`] or within GtkBuilder template files.

Check warning on line 1026 in glib-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

unresolved link to `ObjectExt::property`
///
/// # Generated methods
/// The following methods are generated on the wrapper type specified on `#[properties(wrapper_type = ...)]`:
Expand Down
220 changes: 206 additions & 14 deletions glib-macros/src/object_interface_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,214 @@

use proc_macro2::TokenStream;
use proc_macro_error::abort_call_site;
use quote::quote;
use quote::{quote, ToTokens};

pub const WRONG_EXPRESSION_MSG: &str =
"This macro's attributes should be a sequence of assign expressions punctuated by comma";

pub const UNSUPPORTED_EXPRESSION_MSG: &str =
"This macro's supported attributes are: `plugin_type = <subclass_of_glib::TypePlugin>, lazy_registration = true|false`";

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;

// registers 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_dynamic_object_interface(
attrs: &syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
input: &syn::ItemImpl,
) -> TokenStream {
let crate_ident = crate::utils::crate_ident_new();
let syn::ItemImpl { self_ty, .. } = &input;

let mut plugin_type_opt: Option<syn::Path> = None;
let mut lazy_registration_opt: Option<bool> = None;

for attr in attrs {
match attr {
// attribute must be one of supported assign expressions.
syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => {
match (*left.to_owned(), *right.to_owned()) {
// `plugin_type = <subclass_of_TypePlugin>`
(
syn::Expr::Path(syn::ExprPath { path: path1, .. }),
syn::Expr::Path(syn::ExprPath { path: path2, .. }),
) if path1.is_ident(&"plugin_type") => plugin_type_opt = Some(path2),
// `lazy_registration = true|false`
(
syn::Expr::Path(syn::ExprPath { path, .. }),
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(syn::LitBool { value, .. }),
..
}),
) if path.is_ident(&"lazy_registration") => lazy_registration_opt = Some(value),
_ => abort_call_site!(UNSUPPORTED_EXPRESSION_MSG),
};
}
_ => abort_call_site!(WRONG_EXPRESSION_MSG),
};
}

let (plugin_ty, lazy_registration) = match (plugin_type_opt, lazy_registration_opt) {
(Some(type_plugin), lazy_registration_opt) => (
type_plugin.into_token_stream(),
lazy_registration_opt.unwrap_or_default(),
),
(None, lazy_registration_opt) => (
quote!(#crate_ident::TypeModule),
lazy_registration_opt.unwrap_or_default(),
),
};

// The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt::unuse`]).
// An object interface can be reregistered as a dynamic type (see [`TypePluginExt::register_type`]).
let register_interface = if lazy_registration {
// registers the object interface as a dynamic type on the first use (lazy registration).
// a weak reference on the plugin is stored and will be used later on the first use of the object interface.
// this implementation relies on a static storage of a weak reference on the plugin and of the glib type to know if the object interface has been registered.
quote! {
impl #self_ty {
/// Returns a mutable reference to the registration status: a tuple of the weak reference on the plugin and of the glib type.
/// This is safe because the mutable reference guarantees that no other threads are concurrently accessing the data.
#[inline]
fn get_registration_status_ref_mut() -> &'static mut Option<(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type)> {
static mut REGISTRATION_STATUS: ::std::sync::Mutex<Option<(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type)>> = ::std::sync::Mutex::new(None);
unsafe { REGISTRATION_STATUS.get_mut().unwrap() }
}

/// Registers the object interface as a dynamic type within the plugin only once.
/// Plugin must have been used at least once.
/// Do nothing if plugin has never been used or if the object interface is already registered as a dynamic type.
#[inline]
fn register_interface() -> #crate_ident::Type {
let registration_status_ref_mut = Self::get_registration_status_ref_mut();
match registration_status_ref_mut {
// plugin has never been used, so the object interface cannot be registered as a dynamic type.
None => #crate_ident::Type::INVALID,
// plugin has been used and the object interface has not been registered yet, so registers it as a dynamic type.
Some((type_plugin, type_)) if !type_.is_valid() => {
*type_ = #crate_ident::subclass::register_dynamic_interface::<#plugin_ty, Self>(&(type_plugin.upgrade().unwrap()));
*type_
},
// plugin has been used and the object interface has already been registered as a dynamic type.
Some((_, type_)) => *type_
}
}

/// Depending on the plugin lifecycle state and on the registration status of the object interface:
/// If plugin is used (and has loaded the implementation) for the first time, postpones the registration and stores a weak reference on the plugin.
/// If plugin is reused (and has reloaded the implementation) and the object interface has been already registered as a dynamic type, reregisters it.
/// An object interface can be reregistered several times as a dynamic type.
/// If plugin is reused (and has reloaded the implementation) and the object interface has not been registered yet as a dynamic type, do nothing.
#[inline]
pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
let registration_status_ref_mut = Self::get_registration_status_ref_mut();
match registration_status_ref_mut {
// plugin has never been used (this is the first time), so postpones registration of the object interface as a dynamic type on the first use.
None => {
*registration_status_ref_mut = Some((#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
true
},
// plugin has been used at least one time and the object interface has been registered as a dynamic type at least one time, so re-registers it.
Some((_, type_)) if type_.is_valid() => {
*type_ = #crate_ident::subclass::register_dynamic_interface::<#plugin_ty, Self>(type_plugin);
type_.is_valid()
},
// plugin has been used at least one time but the object interface has not been registered yet as a dynamic type, so keeps postponed registration.
Some(_) => {
true
}
}
}

/// Depending on the plugin lifecycle state and on the registration status of the object interface:
/// If plugin has been used (or reused) but the object interface has not been registered yet as a dynamic type, cancels the postponed registration by deleting the weak reference on the plugin.
/// Else do nothing.
#[inline]
pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
let registration_status_ref_mut = Self::get_registration_status_ref_mut();
match registration_status_ref_mut {
// plugin has never been used, so unload implementation is unexpected.
None => false,
// plugin has been used at least one time and the object interface has been registered as a dynamic type at least one time.
Some((_, type_)) if type_.is_valid() => true,
// plugin has been used at least one time but the object interface has not been registered yet as a dynamic type, so cancels the postponed registration.
Some(_) => {
*registration_status_ref_mut = None;
true
}
}
}
}
}
} else {
// registers immediately the object interface as a dynamic type.
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() }
}

/// Do nothing as the object interface has been registered on implementation load.
#[inline]
fn register_interface() -> #crate_ident::Type {
unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(*Self::get_type_mut()) }
}

/// Registers the object interface as a dynamic type within the plugin.
/// The object interface can be registered several times as a dynamic type.
#[inline]
pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
let type_mut = Self::get_type_mut();
*type_mut = #crate_ident::translate::IntoGlib::into_glib(#crate_ident::subclass::register_dynamic_interface::<#plugin_ty, Self>(type_plugin));
*type_mut != #crate_ident::gobject_ffi::G_TYPE_INVALID
}

/// Do nothing as object interfaces registered as dynamic types are never unregistered.
#[inline]
pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> 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 +255,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
Loading