Skip to content

Commit

Permalink
Merge pull request #1271 from fbrouille/flags_dynamic
Browse files Browse the repository at this point in the history
add support of flags registered as dynamic types
  • Loading branch information
sdroege committed Jan 15, 2024
2 parents acb7f0d + 8d3ac91 commit d08bf24
Show file tree
Hide file tree
Showing 9 changed files with 1,216 additions and 90 deletions.
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
257 changes: 241 additions & 16 deletions glib-macros/src/flags_attribute.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// 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 proc_macro_error::abort_call_site;
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DeriveInput, Ident,
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";

pub struct AttrInput {
pub enum_name: syn::LitStr,
Expand Down Expand Up @@ -114,20 +116,61 @@ fn gen_bitflags(
}
}

pub fn impl_flags(attrs: AttrInput, input: &DeriveInput) -> 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 enum_variants = match input.data {
Data::Enum(ref e) => &e.variants,
_ => abort_call_site!("#[glib::flags] only supports enums"),
};
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 @@ -202,12 +245,34 @@ pub fn impl_flags(attrs: AttrInput, input: &DeriveInput) -> 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 @@ -231,3 +296,163 @@ pub fn impl_flags(attrs: AttrInput, input: &DeriveInput) -> 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
}
}
}
}
}

0 comments on commit d08bf24

Please sign in to comment.