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 flags registered as dynamic types #1271

Merged
merged 3 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion glib-macros/src/enum_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ fn register_enum_as_dynamic(

let enum_values = quote! {
#crate_ident::enums::EnumValuesStorage<#nb_enum_values> = unsafe {
#crate_ident::enums::EnumValuesStorage::<#nb_enum_values>::new::<{#nb_enum_values - 1}>([
#crate_ident::enums::EnumValuesStorage::<#nb_enum_values>::new([
#(#enum_values_iter)*
])
}
Expand Down
249 changes: 238 additions & 11 deletions glib-macros/src/flags_attribute.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use heck::{ToKebabCase, ToUpperCamelCase};
use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Ident, ItemEnum, Variant,
Visibility,
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Ident, Variant, Visibility,
};

use crate::utils::{crate_ident_new, parse_nested_meta_items, NestedMetaItem};
use crate::utils::{
crate_ident_new, parse_nested_meta_items, parse_optional_nested_meta_items, NestedMetaItem,
};

pub const WRONG_PLACE_MSG: &str = "#[glib::flags] only supports enums";

Expand Down Expand Up @@ -115,17 +116,61 @@ fn gen_bitflags(
}
}

pub fn impl_flags(attrs: AttrInput, input: &mut ItemEnum) -> TokenStream {
let gtype_name = attrs.enum_name.value();
let name = &input.ident;
let visibility = &input.vis;
pub fn impl_flags(attrs: AttrInput, input: &mut syn::ItemEnum) -> TokenStream {
let gtype_name = attrs.enum_name;

let syn::ItemEnum {
attrs,
ident: name,
vis: visibility,
..
} = input;

let enum_variants = &input.variants;
let (g_flags_values, nb_flags_values) = gen_flags_values(name, enum_variants);

let crate_ident = crate_ident_new();

let mut plugin_type = NestedMetaItem::<syn::Path>::new("plugin_type").value_required();
let mut lazy_registration =
NestedMetaItem::<syn::LitBool>::new("lazy_registration").value_required();

let found = parse_optional_nested_meta_items(
&*attrs,
"flags_dynamic",
&mut [&mut plugin_type, &mut lazy_registration],
);

let register_flags = match found {
Err(e) => return e.to_compile_error(),
Ok(None) => register_flags_as_static(
&crate_ident,
name,
gtype_name,
g_flags_values,
nb_flags_values,
),
Ok(Some(_)) => {
// remove attribute 'flags_dynamic' from the attribute list because it is not a real proc_macro_attribute
attrs.retain(|attr| !attr.path().is_ident("flags_dynamic"));
let plugin_ty = plugin_type
.value
.map(|p| p.into_token_stream())
.unwrap_or(quote!(#crate_ident::TypeModule));
let lazy_registration = lazy_registration.value.map(|b| b.value).unwrap_or_default();
register_flags_as_dynamic(
&crate_ident,
plugin_ty,
lazy_registration,
name,
gtype_name,
g_flags_values,
nb_flags_values,
)
}
};

let bitflags = gen_bitflags(name, visibility, enum_variants, &crate_ident);
let (flags_values, nb_flags_values) = gen_flags_values(name, enum_variants);

quote! {
#bitflags
Expand Down Expand Up @@ -200,12 +245,34 @@ pub fn impl_flags(attrs: AttrInput, input: &mut ItemEnum) -> TokenStream {
impl #crate_ident::StaticType for #name {
#[inline]
fn static_type() -> #crate_ident::Type {
Self::register_flags()
}
}

#register_flags
}
}

// Registers the flags as a static type.
fn register_flags_as_static(
crate_ident: &TokenStream,
name: &syn::Ident,
gtype_name: syn::LitStr,
g_flags_values: TokenStream,
nb_flags_values: usize,
) -> TokenStream {
// registers the flags on first use (lazy registration).
quote! {
impl #name {
/// Registers the flags only once.
#[inline]
fn register_flags() -> #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(|| {
static mut VALUES: [#crate_ident::gobject_ffi::GFlagsValue; #nb_flags_values] = [
#flags_values
#g_flags_values
#crate_ident::gobject_ffi::GFlagsValue {
value: 0,
value_name: ::std::ptr::null(),
Expand All @@ -229,3 +296,163 @@ pub fn impl_flags(attrs: AttrInput, input: &mut ItemEnum) -> TokenStream {
}
}
}

// The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
// Flags can be reregistered as a dynamic type.
fn register_flags_as_dynamic(
crate_ident: &TokenStream,
plugin_ty: TokenStream,
lazy_registration: bool,
name: &syn::Ident,
gtype_name: syn::LitStr,
g_flags_values: TokenStream,
nb_flags_values: usize,
) -> TokenStream {
// Wrap each GFlagsValue to FlagsValue
let g_flags_values_expr: syn::ExprArray = syn::parse_quote! { [#g_flags_values] };
let flags_values_iter = g_flags_values_expr.elems.iter().map(|v| {
quote_spanned! {syn::spanned::Spanned::span(&v)=>
#crate_ident::FlagsValue::unsafe_from(#v),
}
});

let flags_values = quote! {
#crate_ident::enums::FlagsValuesStorage<#nb_flags_values> = unsafe {
#crate_ident::enums::FlagsValuesStorage::<#nb_flags_values>::new([
#(#flags_values_iter)*
])
}
};

// The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]).
// Flags can be reregistered as a dynamic type.
if lazy_registration {
// registers the flags 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 flags.
// this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the flags have been registered.

// the registration status type.
let registration_status_type = format_ident!("{}RegistrationStatus", name);
// name of the static variable to store the registration status.
let registration_status = format_ident!(
"{}",
registration_status_type.to_string().to_shouty_snake_case()
);
// name of the static array to store the flags values.
let flags_values_array =
format_ident!("{}_VALUES", name.to_string().to_shouty_snake_case());

quote! {
/// The registration status type: a tuple of the weak reference on the plugin and of the GLib type.
struct #registration_status_type(<#plugin_ty as #crate_ident::clone::Downgrade>::Weak, #crate_ident::Type);
unsafe impl Send for #registration_status_type {}

/// The registration status protected by a mutex guarantees so that no other threads are concurrently accessing the data.
static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);

/// Array of `FlagsValue` for the possible flags values.
static #flags_values_array: #flags_values;

impl #name {
/// Registers the flags 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 flags are already registered as a dynamic type.
#[inline]
fn register_flags() -> #crate_ident::Type {
let mut registration_status = #registration_status.lock().unwrap();
match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
// plugin has never been used, so the flags cannot be registered as a dynamic type.
None => #crate_ident::Type::INVALID,
// plugin has been used and the flags have not been registered yet, so registers tem as a dynamic type.
Some(#registration_status_type(type_plugin, type_)) if !type_.is_valid() => {
*type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin.upgrade().unwrap().as_ref(), #gtype_name, #flags_values_array.as_ref());
*type_
},
// plugin has been used and the flags have already been registered as a dynamic type.
Some(#registration_status_type(_, type_)) => *type_
}
}

/// Depending on the plugin lifecycle state and on the registration status of the flags:
/// 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 flags have been already registered as a dynamic type, reregisters them.
/// Flags can be reregistered several times as a dynamic type.
/// If plugin is reused (and has reloaded the implementation) and the flags have not been registered yet as a dynamic type, do nothing.
#[inline]
pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
let mut registration_status = #registration_status.lock().unwrap();
match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
// plugin has never been used (this is the first time), so postpones registration of the flags as a dynamic type on the first use.
None => {
*registration_status = Some(#registration_status_type(#crate_ident::clone::Downgrade::downgrade(type_plugin), #crate_ident::Type::INVALID));
true
},
// plugin has been used at least one time and the flags have been registered as a dynamic type at least one time, so re-registers them.
Some(#registration_status_type(_, type_)) if type_.is_valid() => {
*type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin, #gtype_name, #flags_values_array.as_ref());
type_.is_valid()
},
// plugin has been used at least one time but the flags have 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 flags:
/// If plugin has been used (or reused) but the flags have 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 mut registration_status = #registration_status.lock().unwrap();
match ::std::ops::DerefMut::deref_mut(&mut registration_status) {
// plugin has never been used, so unload implementation is unexpected.
None => false,
// plugin has been used at least one time and the flags have been registered as a dynamic type at least one time.
Some(#registration_status_type(_, type_)) if type_.is_valid() => true,
// plugin has been used at least one time but the flags have not been registered yet as a dynamic type, so cancels the postponed registration.
Some(_) => {
*registration_status = None;
true
}
}
}
}
}
} else {
// registers immediately the flags as a dynamic type.

// name of the static variable to store the GLib type.
let gtype_status = format_ident!("{}_G_TYPE", name.to_string().to_shouty_snake_case());

quote! {
/// The GLib type which can be safely shared between threads.
static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);

impl #name {
/// Do nothing as the flags has been registered on implementation load.
#[inline]
fn register_flags() -> #crate_ident::Type {
let gtype = #gtype_status.load(::std::sync::atomic::Ordering::Acquire);
unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(gtype) }
}

/// Registers the flags as a dynamic type within the plugin.
/// The flags can be registered several times as a dynamic type.
#[inline]
pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool {
static VALUES: #flags_values;
let gtype = #crate_ident::translate::IntoGlib::into_glib(<#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_flags(type_plugin, #gtype_name, VALUES.as_ref()));
#gtype_status.store(gtype, ::std::sync::atomic::Ordering::Release);
gtype != #crate_ident::gobject_ffi::G_TYPE_INVALID
}

/// Do nothing as flags registered as dynamic types are never unregistered.
#[inline]
pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool {
true
}
}
}
}
}
88 changes: 88 additions & 0 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,95 @@ pub fn enum_derive(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// The flags can be registered as a dynamic type by setting the macro helper
/// attribute `flags_dynamic`:
/// ```ignore
/// use glib::prelude::*;
/// use glib::subclass::prelude::*;
///
/// #[glib::flags(name = "MyFlags")]
/// #[flags_dynamic]
/// enum MyFlags {
/// ...
/// }
/// ```
///
/// As a dynamic type, the flags must be explicitly registered when the system
/// loads the implementation (see [`TypePlugin`] and [`TypeModule`].
/// Therefore, whereas the flags can be registered only once as a static type,
/// they can be registered several times as a dynamic type.
///
/// The flags registered as a dynamic type are never unregistered. The system
/// calls [`TypePluginExt::unuse`] to unload the implementation. If the
/// [`TypePlugin`] subclass is a [`TypeModule`], the flags registered as a
/// dynamic type are marked as unloaded and must be registered again when the
/// module is reloaded.
///
/// The macro helper attribute `flags_dynamic` provides two behaviors when
/// registering the flags as a dynamic type:
///
/// - lazy registration: by default the flags are registered as a dynamic type
/// when the system loads the implementation (e.g. when the module is loaded).
/// Optionally setting `lazy_registration` to `true` postpones registration on
/// the first use (when `static_type()` is called for the first time):
/// ```ignore
/// #[glib::flags(name = "MyFlags")]
/// #[flags_dynamic(lazy_registration = true)]
/// enum MyFlags {
/// ...
/// }
/// ```
///
/// - registration within [`TypeModule`] subclass or within [`TypePlugin`]
/// subclass: the flags are usually registered as a dynamic type within a
/// [`TypeModule`] subclass:
/// ```ignore
/// #[glib::flags(name = "MyModuleFlags")]
/// #[flags_dynamic]
/// enum MyModuleFlags {
/// ...
/// }
/// ...
/// #[derive(Default)]
/// pub struct MyModule;
/// ...
/// impl TypeModuleImpl for MyModule {
/// fn load(&self) -> bool {
/// // registers flags as dynamic types.
/// let my_module = self.obj();
/// let type_module: &glib::TypeModule = my_module.upcast_ref();
/// MyModuleFlags::on_implementation_load(type_module)
/// }
/// ...
/// }
/// ```
///
/// Optionally setting `plugin_type` allows to register the flags as a dynamic
/// type within a [`TypePlugin`] subclass that is not a [`TypeModule`]:
/// ```ignore
/// #[glib::flags(name = "MyModuleFlags")]
/// #[flags_dynamic(plugin_type = MyPlugin)]
/// enum MyModuleFlags {
/// ...
/// }
/// ...
/// #[derive(Default)]
/// pub struct MyPlugin;
/// ...
/// impl TypePluginImpl for MyPlugin {
/// fn use_plugin(&self) {
/// // register flags as dynamic types.
/// let my_plugin = self.obj();
/// MyPluginFlags::on_implementation_load(my_plugin.as_ref());
/// }
/// ...
/// }
/// ```
///
/// [`glib::Value`]: ../glib/value/struct.Value.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.
#[proc_macro_attribute]
#[proc_macro_error]
pub fn flags(attr: TokenStream, item: TokenStream) -> TokenStream {
Expand Down