From d982e089b87a17c762f6a46abc5aa40b55c848a1 Mon Sep 17 00:00:00 2001 From: fred Date: Sat, 16 Dec 2023 11:36:49 +0100 Subject: [PATCH] Add support of enums as dynamic types (#1220) Add support of enums as dynamic types --- glib-macros/src/enum_derive.rs | 274 ++++++++-- glib-macros/src/lib.rs | 107 +++- glib-macros/src/object_interface_attribute.rs | 10 +- glib-macros/src/object_subclass_attribute.rs | 8 +- glib-macros/tests/dynamic_enums.rs | 499 ++++++++++++++++++ glib-macros/tests/dynamic_objects.rs | 24 +- glib/Gir_GObject.toml | 1 + glib/src/enums.rs | 124 ++++- glib/src/gobject/dynamic_object.rs | 26 +- glib/src/gobject/type_module.rs | 19 +- glib/src/lib.rs | 6 +- glib/src/subclass/mod.rs | 8 +- glib/src/subclass/type_module.rs | 2 +- glib/src/subclass/type_plugin.rs | 31 +- 14 files changed, 1055 insertions(+), 84 deletions(-) create mode 100644 glib-macros/tests/dynamic_enums.rs diff --git a/glib-macros/src/enum_derive.rs b/glib-macros/src/enum_derive.rs index de61835603c7..6b693fa13202 100644 --- a/glib-macros/src/enum_derive.rs +++ b/glib-macros/src/enum_derive.rs @@ -1,14 +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 syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Ident, Variant}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use syn::{ + parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, ExprArray, Ident, + Variant, +}; use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_nested_meta_items, NestedMetaItem}; -// Generate glib::gobject_ffi::GEnumValue structs mapping the enum such as: +// generates glib::gobject_ffi::GEnumValue structs mapping the enum such as: // glib::gobject_ffi::GEnumValue { // value: Animal::Goat as i32, // value_name: "Goat\0" as *const _ as *const _, @@ -20,7 +23,7 @@ fn gen_enum_values( ) -> (TokenStream, usize) { let crate_ident = crate_ident_new(); - // start at one as GEnumValue array is null-terminated + // starts at one as GEnumValue array is null-terminated. let mut n = 1; let recurse = enum_variants.iter().map(|v| { let name = &v.ident; @@ -43,6 +46,7 @@ fn gen_enum_values( let value_nick = format!("{value_nick}\0"); n += 1; + // generates a glib::gobject_ffi::GEnumValue. quote_spanned! {v.span()=> #crate_ident::gobject_ffi::GEnumValue { value: #enum_name::#name as i32, @@ -85,6 +89,238 @@ pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream { let crate_ident = crate_ident_new(); + // registers the enum on first use (lazy registration). + let register_enum = quote! { + impl #name { + /// Registers the enum only once. + #[inline] + fn register_enum() -> #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::GEnumValue; #nb_enum_values] = [ + #enum_values + #crate_ident::gobject_ffi::GEnumValue { + value: 0, + value_name: ::std::ptr::null(), + value_nick: ::std::ptr::null(), + }, + ]; + let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); + unsafe { + let type_ = #crate_ident::gobject_ffi::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); + let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); + assert!(type_.is_valid()); + TYPE = type_; + } + }); + + unsafe { + TYPE + } + } + } + }; + + impl_enum_(name, from_glib, register_enum) +} + +pub fn impl_dynamic_enum(input: &syn::DeriveInput) -> TokenStream { + let name = &input.ident; + + let enum_variants = match input.data { + Data::Enum(ref e) => &e.variants, + _ => abort_call_site!("#[derive(glib::Enum)] only supports enums"), + }; + + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let mut plugin_type = NestedMetaItem::::new("plugin_type").value_required(); + let mut lazy_registration = + NestedMetaItem::::new("lazy_registration").value_required(); + let found = parse_nested_meta_items( + &input.attrs, + "enum_type", + &mut [&mut gtype_name, &mut plugin_type, &mut lazy_registration], + ); + + match found { + Ok(None) => { + abort_call_site!("#[derive(glib::DynamicEnum)] requires #[enum_type(name = \"EnumTypeName\"[, plugin_type = ][, lazy_registration = true|false])]") + } + Err(e) => return e.to_compile_error(), + Ok(attr) => attr, + }; + + let crate_ident = crate_ident_new(); + + let gtype_name = gtype_name.value.unwrap(); + 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(); + + let from_glib = gen_enum_from_glib(name, enum_variants); + let (g_enum_values, nb_enum_values) = gen_enum_values(name, enum_variants); + + // Wrap each GEnumValue to EnumValue + let g_enum_values_expr: ExprArray = parse_quote! { [#g_enum_values] }; + let enum_values_iter = g_enum_values_expr.elems.iter().map(|v| { + quote_spanned! {v.span()=> + #crate_ident::EnumValue::unsafe_from(#v), + } + }); + + let enum_values = quote! { + #crate_ident::enums::EnumValuesStorage<#nb_enum_values> = unsafe { + #crate_ident::enums::EnumValuesStorage::<#nb_enum_values>::new::<{#nb_enum_values - 1}>([ + #(#enum_values_iter)* + ]) + } + }; + + // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]). + // An enum can be reregistered as a dynamic type. + let register_enum_impl = if lazy_registration { + // registers the enum 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 enum. + // this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the enum has 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 enumeration values. + let enum_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> = ::std::sync::Mutex::new(None); + + /// Array of `EnumValue` for the possible enumeration values. + static #enum_values_array: #enum_values; + + impl #name { + /// Registers the enum 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 enum is already registered as a dynamic type. + #[inline] + fn register_enum() -> #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 enum cannot be registered as a dynamic type. + None => #crate_ident::Type::INVALID, + // plugin has been used and the enum has not been registered yet, so registers it as a dynamic type. + Some(#registration_status_type(type_plugin, type_)) if !type_.is_valid() => { + *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin.upgrade().unwrap().as_ref(), #gtype_name, #enum_values_array.as_ref()); + *type_ + }, + // plugin has been used and the enum has 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 enum: + /// 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 enum has been already registered as a dynamic type, reregisters it. + /// An enum can be reregistered several times as a dynamic type. + /// If plugin is reused (and has reloaded the implementation) and the enum has 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 enum 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 enum has been registered as a dynamic type at least one time, so re-registers it. + Some(#registration_status_type(_, type_)) if type_.is_valid() => { + *type_ = <#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, #enum_values_array.as_ref()); + type_.is_valid() + }, + // plugin has been used at least one time but the enum 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 enum: + /// If plugin has been used (or reused) but the enum 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 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 enum has 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 enum has not been registered yet as a dynamic type, so cancels the postponed registration. + Some(_) => { + *registration_status = None; + true + } + } + } + } + } + } else { + // registers immediately the enum 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 enum has been registered on implementation load. + #[inline] + fn register_enum() -> #crate_ident::Type { + let gtype = #gtype_status.load(::std::sync::atomic::Ordering::Relaxed); + unsafe { <#crate_ident::Type as #crate_ident::translate::FromGlib<#crate_ident::ffi::GType>>::from_glib(gtype) } + } + + /// Registers the enum as a dynamic type within the plugin. + /// The enum can be registered several times as a dynamic type. + #[inline] + pub fn on_implementation_load(type_plugin: &#plugin_ty) -> bool { + static VALUES: #enum_values; + let gtype = #crate_ident::translate::IntoGlib::into_glib(<#plugin_ty as glib::prelude::DynamicObjectRegisterExt>::register_dynamic_enum(type_plugin, #gtype_name, VALUES.as_ref())); + #gtype_status.store(gtype, ::std::sync::atomic::Ordering::Relaxed); + gtype != #crate_ident::gobject_ffi::G_TYPE_INVALID + } + + /// Do nothing as enums registered as dynamic types are never unregistered. + #[inline] + pub fn on_implementation_unload(type_plugin_: &#plugin_ty) -> bool { + true + } + } + } + }; + + impl_enum_(name, from_glib, register_enum_impl) +} + +pub fn impl_enum_( + name: &syn::Ident, + from_glib: TokenStream, + register_enum: TokenStream, +) -> TokenStream { + let crate_ident = crate_ident_new(); + quote! { impl #crate_ident::translate::IntoGlib for #name { type GlibType = i32; @@ -161,34 +397,12 @@ pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream { impl #crate_ident::StaticType for #name { #[inline] fn static_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(|| { - static mut VALUES: [#crate_ident::gobject_ffi::GEnumValue; #nb_enum_values] = [ - #enum_values - #crate_ident::gobject_ffi::GEnumValue { - value: 0, - value_name: ::std::ptr::null(), - value_nick: ::std::ptr::null(), - }, - ]; - - let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); - unsafe { - let type_ = #crate_ident::gobject_ffi::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); - let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); - assert!(type_.is_valid()); - TYPE = type_; - } - }); - - unsafe { - TYPE - } + Self::register_enum() } } + #register_enum + impl #crate_ident::HasParamSpec for #name { type ParamSpec = #crate_ident::ParamSpecEnum; type SetValue = Self; diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index e0e9224ca52a..4f555a67fafd 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -439,7 +439,7 @@ pub fn closure_local(item: TokenStream) -> TokenStream { closure::closure_inner(item, "new_local") } -/// Derive macro for register a rust enum in the glib type system and derive the +/// Derive macro for register a Rust enum in the GLib type system and derive the /// the [`glib::Value`] traits. /// /// # Example @@ -468,6 +468,108 @@ pub fn enum_derive(input: TokenStream) -> TokenStream { gen.into() } +/// Derive macro for register a Rust enum in the GLib type system as a dynamic +/// type and derive the [`glib::Value`] traits. +/// +/// An enum must be explicitly registered as a dynamic type when the system +/// loads its implementation (see [`TypePlugin`] and [`TypeModule`]. +/// Therefore, whereas an enum can be registered only once as a static type, +/// it can be registered several times as a dynamic type. +/// +/// An enum 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 enum 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 enum as a dynamic +/// type: +/// +/// By default an enum is registered as a dynamic type when the system loads +/// its implementation (e.g. when the module is loaded): +/// ```ignore +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// +/// #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::DynamicEnum)] +/// #[enum_type(name = "MyEnum")] +/// enum MyEnum { +/// Val, +/// #[enum_value(name = "My Val")] +/// ValWithCustomName, +/// #[enum_value(name = "My Other Val", nick = "other")] +/// ValWithCustomNameAndNick, +/// } +/// ``` +/// +/// Optionally setting the macro attribute `lazy_registration` to `true` +/// postpones registration on the first use (when `static_type()` is called for +/// the first time), similarly to the [`macro@enum_derive`] macro: +/// ```ignore +/// #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::DynamicEnum)] +/// #[enum_type(name = "MyEnum", lazy_registration = true)] +/// enum MyEnum { +/// ... +/// } +/// ``` +/// +/// An enum is usually registered as a dynamic type within a [`TypeModule`] +/// subclass: +/// ```ignore +/// #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::DynamicEnum)] +/// #[enum_type(name = "MyModuleEnum")] +/// enum MyModuleEnum { +/// ... +/// } +/// ... +/// #[derive(Default)] +/// pub struct MyModule; +/// ... +/// impl TypeModuleImpl for MyModule { +/// fn load(&self) -> bool { +/// // registers enums as dynamic types. +/// let my_module = self.obj(); +/// let type_module: &glib::TypeModule = my_module.upcast_ref(); +/// MyModuleEnum::on_implementation_load(type_module) +/// } +/// ... +/// } +/// ``` +/// +/// Optionally setting the macro attribute `plugin_type` allows to register an +/// enum as a dynamic type within a given [`TypePlugin`] subclass: +/// ```ignore +/// #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::DynamicEnum)] +/// #[enum_type(name = "MyPluginEnum", plugin_type = MyPlugin)] +/// enum MyPluginEnum { +/// ... +/// } +/// ... +/// #[derive(Default)] +/// pub struct MyPlugin; +/// ... +/// impl TypePluginImpl for MyPlugin { +/// fn use_plugin(&self) { +/// // register enums as dynamic types. +/// let my_plugin = self.obj(); +/// MyPluginEnum::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.html#method.unuse +#[proc_macro_derive(DynamicEnum, attributes(enum_type, enum_value))] +#[proc_macro_error] +pub fn dynamic_enum_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let gen = enum_derive::impl_dynamic_enum(&input); + gen.into() +} + /// Attribute macro for defining flags using the `bitflags` crate. /// This macro will also define a `GFlags::type_` function and /// the [`glib::Value`] traits. @@ -671,8 +773,7 @@ pub fn object_subclass(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// 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: +/// first time), similarly to the [`macro@object_subclass`] macro: /// ```ignore /// #[glib::dynamic_object_subclass(lazy_registration = true)] /// impl ObjectSubclass for MyType { ... } diff --git a/glib-macros/src/object_interface_attribute.rs b/glib-macros/src/object_interface_attribute.rs index 321e6624d125..d18659ae5ce3 100644 --- a/glib-macros/src/object_interface_attribute.rs +++ b/glib-macros/src/object_interface_attribute.rs @@ -86,15 +86,15 @@ pub fn impl_dynamic_object_interface( ), }; - // 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`]). + // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]). + // An object interface can be reregistered as a dynamic 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. + // 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. + /// 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)> { @@ -171,7 +171,7 @@ pub fn impl_dynamic_object_interface( // registers immediately the object interface as a dynamic type. quote! { impl #self_ty { - /// Returns a mutable reference to the glib type. + /// 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 { diff --git a/glib-macros/src/object_subclass_attribute.rs b/glib-macros/src/object_subclass_attribute.rs index d9ad6bf7ad19..2331570e13a7 100644 --- a/glib-macros/src/object_subclass_attribute.rs +++ b/glib-macros/src/object_subclass_attribute.rs @@ -81,15 +81,15 @@ pub fn impl_dynamic_object_subclass( ), }; - // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt::unuse`]). - // An object subclass can be reregistered as a dynamic type (see [`TypePluginExt::register_type`]). + // The following implementations follows the lifecycle of plugins and of dynamic types (see [`TypePluginExt`] and [`TypeModuleExt`]). + // An object subclass can be reregistered as a dynamic type. let register_type = if lazy_registration { // registers the object subclass 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 subclass. - // this implementation relies on a static storage of a weak reference on the plugin and of the glib type to know if the object subclass has been registered. + // this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the object subclass 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. + /// 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)> { diff --git a/glib-macros/tests/dynamic_enums.rs b/glib-macros/tests/dynamic_enums.rs new file mode 100644 index 000000000000..8db937085f9b --- /dev/null +++ b/glib-macros/tests/dynamic_enums.rs @@ -0,0 +1,499 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use glib::{prelude::*, subclass::prelude::*, Cast}; + +mod module { + use super::*; + + mod imp { + use super::*; + + // 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 TypePluginImpl for MyModule {} + + impl TypeModuleImpl for MyModule { + fn load(&self) -> bool { + // registers enums as dynamic types. + let my_module = self.obj(); + let type_module: &glib::TypeModule = my_module.upcast_ref(); + super::MyModuleEnum::on_implementation_load(type_module) + && super::MyModuleEnumLazy::on_implementation_load(type_module) + } + + fn unload(&self) { + // marks the enums as unregistered. + let my_module = self.obj(); + let type_module: &glib::TypeModule = my_module.upcast_ref(); + super::MyModuleEnumLazy::on_implementation_unload(type_module); + super::MyModuleEnum::on_implementation_unload(type_module); + } + } + } + + // an enum to register as a dynamic type. + #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::DynamicEnum)] + #[repr(u32)] + #[enum_type(name = "MyModuleEnum")] + pub enum MyModuleEnum { + #[enum_value(name = "Foo")] + Foo, + Bar, + } + + // an enum to lazy register as a dynamic type. + #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::DynamicEnum)] + #[repr(u32)] + #[enum_type(name = "MyModuleEnumLazy", lazy_registration = true)] + pub enum MyModuleEnumLazy { + #[enum_value(name = "Foo")] + Foo, + Bar, + } + + // a module (must extend `glib::TypeModule` and must implement `glib::TypePlugin`). + glib::wrapper! { + pub struct MyModule(ObjectSubclass) + @extends glib::TypeModule, @implements glib::TypePlugin; + } + + #[test] + fn dynamic_types() { + // 1st: creates a single module to test with. + let module = glib::Object::new::(); + // 1st: uses it to test lifecycle of enums registered as dynamic types. + enum_lifecycle(&module); + // 2nd: uses it to test behavior of enums registered as dynamic types. + enum_behavior(&module); + } + + // tests lifecycle of enums registered as dynamic types within a module. + fn enum_lifecycle(module: &MyModule) { + // checks types of enums to register as dynamic types are invalid (module is not loaded yet). + assert!(!MyModuleEnum::static_type().is_valid()); + assert!(!MyModuleEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to load/unload the module. + TypeModuleExt::use_(module); + TypeModuleExt::unuse(module); + + // checks types of enums registered as dynamic types are valid (module is unloaded). + assert!(MyModuleEnum::static_type().is_valid()); + // checks types of enums that are lazy registered as dynamic types are valid (module is unloaded). + assert!(!MyModuleEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to load the module. + TypeModuleExt::use_(module); + + // checks types of enums registered as dynamic types are valid (module is loaded). + let enum_type = MyModuleEnum::static_type(); + assert!(enum_type.is_valid()); + let enum_lazy_type = MyModuleEnumLazy::static_type(); + assert!(enum_lazy_type.is_valid()); + + // checks plugin of enums registered as dynamic types is `MyModule`. + assert_eq!( + enum_type.plugin().as_ref(), + Some(module.upcast_ref::()) + ); + assert_eq!( + enum_lazy_type.plugin().as_ref(), + Some(module.upcast_ref::()) + ); + + // simulates the GLib type system to unload the module. + TypeModuleExt::unuse(module); + + // checks types of enums registered as dynamic types are still valid (should have been marked as unloaded by the GLib type system but this cannot be checked). + assert!(MyModuleEnum::static_type().is_valid()); + assert!(MyModuleEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to reload the module. + TypeModuleExt::use_(module); + + // checks types of enums registered as dynamic types are still valid (should have been marked as loaded by the GLib type system but this cannot be checked). + assert!(MyModuleEnum::static_type().is_valid()); + assert!(MyModuleEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to unload the module. + TypeModuleExt::unuse(module); + } + + // tests behavior of enums registered as dynamic types within a module. + fn enum_behavior(module: &MyModule) { + use glib::prelude::*; + use glib::translate::{FromGlib, IntoGlib}; + + // simulates the GLib type system to load the module. + TypeModuleExt::use_(module); + + assert_eq!(MyModuleEnum::Foo.into_glib(), 0); + assert_eq!(MyModuleEnum::Bar.into_glib(), 1); + + assert_eq!(unsafe { MyModuleEnum::from_glib(0) }, MyModuleEnum::Foo); + assert_eq!(unsafe { MyModuleEnum::from_glib(1) }, MyModuleEnum::Bar); + + let t = MyModuleEnum::static_type(); + assert!(t.is_a(glib::Type::ENUM)); + assert_eq!(t.name(), "MyModuleEnum"); + + let e = glib::EnumClass::with_type(t).expect("EnumClass::new failed"); + + let values = e.values(); + assert_eq!(values.len(), 2); + assert_eq!(values[0].name(), "Foo"); + assert_eq!(values[0].nick(), "foo"); + assert_eq!(values[1].name(), "Bar"); + assert_eq!(values[1].nick(), "bar"); + + let v = e.value(0).expect("EnumClass::get_value(0) failed"); + assert_eq!(v.name(), "Foo"); + assert_eq!(v.nick(), "foo"); + let v = e.value(1).expect("EnumClass::get_value(1) failed"); + assert_eq!(v.name(), "Bar"); + assert_eq!(v.nick(), "bar"); + assert_eq!(e.value(2), None); + + // within enums registered as dynamic types, values are usables only if + // at least one type class ref exists (see `glib::EnumClass`). + assert_eq!( + MyModuleEnum::Foo.to_value().get::(), + Ok(MyModuleEnum::Foo) + ); + assert_eq!( + MyModuleEnum::Bar.to_value().get::(), + Ok(MyModuleEnum::Bar) + ); + + assert_eq!(MyModuleEnumLazy::Foo.into_glib(), 0); + assert_eq!(MyModuleEnumLazy::Bar.into_glib(), 1); + + assert_eq!( + unsafe { MyModuleEnumLazy::from_glib(0) }, + MyModuleEnumLazy::Foo + ); + assert_eq!( + unsafe { MyModuleEnumLazy::from_glib(1) }, + MyModuleEnumLazy::Bar + ); + + let t = MyModuleEnumLazy::static_type(); + assert!(t.is_a(glib::Type::ENUM)); + assert_eq!(t.name(), "MyModuleEnumLazy"); + + let e = glib::EnumClass::with_type(t).expect("EnumClass::new failed"); + + let values = e.values(); + assert_eq!(values.len(), 2); + assert_eq!(values[0].name(), "Foo"); + assert_eq!(values[0].nick(), "foo"); + assert_eq!(values[1].name(), "Bar"); + assert_eq!(values[1].nick(), "bar"); + + let v = e.value(0).expect("EnumClass::get_value(0) failed"); + assert_eq!(v.name(), "Foo"); + assert_eq!(v.nick(), "foo"); + let v = e.value(1).expect("EnumClass::get_value(1) failed"); + assert_eq!(v.name(), "Bar"); + assert_eq!(v.nick(), "bar"); + assert_eq!(e.value(2), None); + + // within enums registered as dynamic types, values are usables only if + // at least one type class ref exists (see `glib::EnumClass`). + assert_eq!( + MyModuleEnumLazy::Foo.to_value().get::(), + Ok(MyModuleEnumLazy::Foo) + ); + assert_eq!( + MyModuleEnumLazy::Bar.to_value().get::(), + Ok(MyModuleEnumLazy::Bar) + ); + + // simulates the GLib type system to unload the module. + TypeModuleExt::unuse(module); + } +} + +pub mod plugin { + use super::*; + + pub mod imp { + use glib::EnumClass; + + use super::*; + use std::cell::Cell; + + // impl for a type plugin (must implement `glib::TypePlugin`). + #[derive(Default)] + pub struct MyPlugin { + my_enum_type_values: Cell>, + my_enum_lazy_type_values: Cell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for MyPlugin { + const NAME: &'static str = "MyPlugin"; + type Type = super::MyPlugin; + type Interfaces = (glib::TypePlugin,); + } + + impl ObjectImpl for MyPlugin {} + + impl TypePluginImpl for MyPlugin { + fn use_plugin(&self) { + // register enums as dynamic types. + let my_plugin = self.obj(); + super::MyPluginEnum::on_implementation_load(my_plugin.as_ref()); + super::MyPluginEnumLazy::on_implementation_load(my_plugin.as_ref()); + } + + fn unuse_plugin(&self) { + // marks enums as unregistered. + let my_plugin = self.obj(); + super::MyPluginEnumLazy::on_implementation_unload(my_plugin.as_ref()); + super::MyPluginEnum::on_implementation_unload(my_plugin.as_ref()); + } + + fn complete_type_info( + &self, + type_: glib::Type, + ) -> (glib::TypeInfo, glib::TypeValueTable) { + let enum_type_values = match type_ { + type_ if type_ == super::MyPluginEnum::static_type() => { + self.my_enum_type_values.get() + } + type_ if type_ == super::MyPluginEnumLazy::static_type() => { + self.my_enum_lazy_type_values.get() + } + _ => panic!("unexpected type"), + } + .expect("enum type values"); + let type_info = EnumClass::complete_type_info(type_, enum_type_values) + .expect("EnumClass::complete_type_info failed"); + (type_info, glib::TypeValueTable::default()) + } + } + + impl TypePluginRegisterImpl for MyPlugin { + fn register_dynamic_enum( + &self, + type_name: &str, + const_static_values: &'static glib::enums::EnumValues, + ) -> glib::Type { + let type_ = glib::Type::from_name(type_name).unwrap_or_else(|| { + glib::Type::register_dynamic( + glib::Type::ENUM, + type_name, + self.obj().upcast_ref::(), + glib::TypeFlags::NONE, + ) + }); + if type_.is_valid() { + match type_name { + "MyPluginEnum" => self.my_enum_type_values.set(Some(const_static_values)), + "MyPluginEnumLazy" => { + self.my_enum_lazy_type_values.set(Some(const_static_values)) + } + _ => panic!("unexpected"), + }; + } + type_ + } + } + } + + // an enum to register as a dynamic type. + #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::DynamicEnum)] + #[repr(u32)] + #[enum_type(name = "MyPluginEnum", plugin_type = MyPlugin)] + pub enum MyPluginEnum { + #[enum_value(name = "Foo")] + Foo, + Bar, + } + + // an enum to lazy register as a dynamic type. + #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::DynamicEnum)] + #[repr(u32)] + #[enum_type(name = "MyPluginEnumLazy", plugin_type = MyPlugin, lazy_registration = true)] + pub enum MyPluginEnumLazy { + #[enum_value(name = "Foo")] + Foo, + Bar, + } + + // a plugin (must implement `glib::TypePlugin`). + glib::wrapper! { + pub struct MyPlugin(ObjectSubclass) @implements glib::TypePlugin; + } + + #[test] + fn dynamic_types() { + // 1st: creates a single plugin to test with. + let plugin = glib::Object::new::(); + // 1st: uses it to test lifecycle of enums registered as dynamic types. + enum_lifecycle(&plugin); + // 2nd: uses it to test behavior of enums registered as dynamic types. + enum_behavior(&plugin); + } + + // tests lifecycle of enums registered as dynamic types within a plugin. + fn enum_lifecycle(plugin: &MyPlugin) { + use glib::prelude::*; + + // checks types of enums to register as dynamic types are invalid (plugin is not used yet). + assert!(!MyPluginEnum::static_type().is_valid()); + assert!(!MyPluginEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to use/unuse the plugin. + TypePluginExt::use_(plugin); + TypePluginExt::unuse(plugin); + + // checks types of enums registered as dynamic types are valid (plugin is unused). + assert!(MyPluginEnum::static_type().is_valid()); + // checks types of enums that are lazy registered as dynamic types are still invalid (plugin is unused). + assert!(!MyPluginEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to use the plugin. + TypePluginExt::use_(plugin); + + // checks types of enums registered as dynamic types are valid (plugin is used). + let enum_type = MyPluginEnum::static_type(); + assert!(enum_type.is_valid()); + let enum_lazy_type = MyPluginEnumLazy::static_type(); + assert!(enum_lazy_type.is_valid()); + + // checks plugin of enums registered as dynamic types is `MyPlugin`. + assert_eq!( + enum_type.plugin().as_ref(), + Some(plugin.upcast_ref::()) + ); + assert_eq!( + enum_lazy_type.plugin().as_ref(), + Some(plugin.upcast_ref::()) + ); + + // simulates the GLib type system to unuse the plugin. + TypePluginExt::unuse(plugin); + + // checks types of enums registered as dynamic types are still valid. + assert!(MyPluginEnum::static_type().is_valid()); + assert!(MyPluginEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to reuse the plugin. + TypePluginExt::use_(plugin); + + // checks types of enums registered as dynamic types are still valid. + assert!(MyPluginEnum::static_type().is_valid()); + assert!(MyPluginEnumLazy::static_type().is_valid()); + + // simulates the GLib type system to unuse the plugin. + TypePluginExt::unuse(plugin); + } + + // tests behavior of enums registered as dynamic types within a plugin. + fn enum_behavior(plugin: &MyPlugin) { + use glib::prelude::*; + use glib::translate::{FromGlib, IntoGlib}; + + // simulates the GLib type system to use the plugin. + TypePluginExt::use_(plugin); + + assert_eq!(MyPluginEnum::Foo.into_glib(), 0); + assert_eq!(MyPluginEnum::Bar.into_glib(), 1); + + assert_eq!(unsafe { MyPluginEnum::from_glib(0) }, MyPluginEnum::Foo); + assert_eq!(unsafe { MyPluginEnum::from_glib(1) }, MyPluginEnum::Bar); + + let t = MyPluginEnum::static_type(); + assert!(t.is_a(glib::Type::ENUM)); + assert_eq!(t.name(), "MyPluginEnum"); + + let e = glib::EnumClass::with_type(t).expect("EnumClass::new failed"); + + let values = e.values(); + assert_eq!(values.len(), 2); + assert_eq!(values[0].name(), "Foo"); + assert_eq!(values[0].nick(), "foo"); + assert_eq!(values[1].name(), "Bar"); + assert_eq!(values[1].nick(), "bar"); + + let v = e.value(0).expect("EnumClass::get_value(0) failed"); + assert_eq!(v.name(), "Foo"); + assert_eq!(v.nick(), "foo"); + let v = e.value(1).expect("EnumClass::get_value(1) failed"); + assert_eq!(v.name(), "Bar"); + assert_eq!(v.nick(), "bar"); + assert_eq!(e.value(2), None); + + // within enums registered as dynamic types, values are usables only if + // at least one type class ref exists (see `glib::EnumClass`). + assert_eq!( + MyPluginEnum::Foo.to_value().get::(), + Ok(MyPluginEnum::Foo) + ); + assert_eq!( + MyPluginEnum::Bar.to_value().get::(), + Ok(MyPluginEnum::Bar) + ); + + assert_eq!(MyPluginEnumLazy::Foo.into_glib(), 0); + assert_eq!(MyPluginEnumLazy::Bar.into_glib(), 1); + + assert_eq!( + unsafe { MyPluginEnumLazy::from_glib(0) }, + MyPluginEnumLazy::Foo + ); + assert_eq!( + unsafe { MyPluginEnumLazy::from_glib(1) }, + MyPluginEnumLazy::Bar + ); + + let t = MyPluginEnumLazy::static_type(); + assert!(t.is_a(glib::Type::ENUM)); + assert_eq!(t.name(), "MyPluginEnumLazy"); + + let e = glib::EnumClass::with_type(t).expect("EnumClass::new failed"); + + let values = e.values(); + assert_eq!(values.len(), 2); + assert_eq!(values[0].name(), "Foo"); + assert_eq!(values[0].nick(), "foo"); + assert_eq!(values[1].name(), "Bar"); + assert_eq!(values[1].nick(), "bar"); + + let v = e.value(0).expect("EnumClass::get_value(0) failed"); + assert_eq!(v.name(), "Foo"); + assert_eq!(v.nick(), "foo"); + let v = e.value(1).expect("EnumClass::get_value(1) failed"); + assert_eq!(v.name(), "Bar"); + assert_eq!(v.nick(), "bar"); + assert_eq!(e.value(2), None); + + // within enums registered as dynamic types, values are usables only if + // at least one type class ref exists (see `glib::EnumClass`). + assert_eq!( + MyPluginEnumLazy::Foo.to_value().get::(), + Ok(MyPluginEnumLazy::Foo) + ); + assert_eq!( + MyPluginEnumLazy::Bar.to_value().get::(), + Ok(MyPluginEnumLazy::Bar) + ); + + // simulates the GLib type system to unuse the plugin. + TypePluginExt::unuse(plugin); + } +} diff --git a/glib-macros/tests/dynamic_objects.rs b/glib-macros/tests/dynamic_objects.rs index cbc432240225..3bec08f47c6f 100644 --- a/glib-macros/tests/dynamic_objects.rs +++ b/glib-macros/tests/dynamic_objects.rs @@ -215,7 +215,7 @@ mod module { assert!(!imp::MyModuleInterfaceLazy::type_().is_valid()); assert!(!imp::MyModuleTypeLazy::type_().is_valid()); - // simulates the glib type system to load/unload the module. + // simulates the GLib type system to load/unload the module. let module = glib::Object::new::(); TypeModuleExt::use_(&module); TypeModuleExt::unuse(&module); @@ -227,7 +227,7 @@ mod module { assert!(!imp::MyModuleInterfaceLazy::type_().is_valid()); assert!(!imp::MyModuleTypeLazy::type_().is_valid()); - // simulates the glib type system to load the module. + // simulates the GLib type system to load the module. TypeModuleExt::use_(&module); // checks types of object subclasses and of object interfaces registered as dynamic types are valid (module is loaded). @@ -258,25 +258,25 @@ mod module { Some(module.upcast_ref::()) ); - // simulates the glib type system to unload the module. + // simulates the GLib type system to unload the module. TypeModuleExt::unuse(&module); - // checks types of object subclasses and of object interfaces registered as dynamic types are still valid (should have been marked as unloaded by the glib type system but this cannot be checked). + // checks types of object subclasses and of object interfaces registered as dynamic types are still valid (should have been marked as unloaded by the GLib type system but this cannot be checked). assert!(imp::MyModuleInterface::type_().is_valid()); assert!(imp::MyModuleType::type_().is_valid()); assert!(imp::MyModuleInterfaceLazy::type_().is_valid()); assert!(imp::MyModuleTypeLazy::type_().is_valid()); - // simulates the glib type system to reload the module. + // simulates the GLib type system to reload the module. TypeModuleExt::use_(&module); - // checks types of object subclasses and of object interfaces registered as dynamic types are still valid (should have been marked as unloaded by the glib type system but this cannot be checked). + // checks types of object subclasses and of object interfaces registered as dynamic types are still valid (should have been marked as unloaded by the GLib type system but this cannot be checked). assert!(imp::MyModuleInterface::type_().is_valid()); assert!(imp::MyModuleType::type_().is_valid()); assert!(imp::MyModuleInterfaceLazy::type_().is_valid()); assert!(imp::MyModuleTypeLazy::type_().is_valid()); - // simulates the glib type system to unload the module. + // simulates the GLib type system to unload the module. TypeModuleExt::unuse(&module); } } @@ -539,7 +539,7 @@ pub mod plugin { assert!(!imp::MyPluginInterfaceLazy::type_().is_valid()); assert!(!imp::MyPluginTypeLazy::type_().is_valid()); - // simulates the glib type system to use/unuse the plugin. + // simulates the GLib type system to use/unuse the plugin. let plugin = glib::Object::new::(); TypePluginExt::use_(&plugin); TypePluginExt::unuse(&plugin); @@ -551,7 +551,7 @@ pub mod plugin { assert!(!imp::MyPluginInterfaceLazy::type_().is_valid()); assert!(!imp::MyPluginTypeLazy::type_().is_valid()); - // simulates the glib type system to use the plugin. + // simulates the GLib type system to use the plugin. TypePluginExt::use_(&plugin); // checks types of object subclasses and of object interfaces registered as dynamic types are valid (plugin is used). @@ -582,7 +582,7 @@ pub mod plugin { Some(plugin.upcast_ref::()) ); - // simulates the glib type system to unuse the plugin. + // simulates the GLib type system to unuse the plugin. TypePluginExt::unuse(&plugin); // checks types of object subclasses and of object interfaces registered as dynamic types are still valid. @@ -591,7 +591,7 @@ pub mod plugin { assert!(imp::MyPluginInterfaceLazy::type_().is_valid()); assert!(imp::MyPluginTypeLazy::type_().is_valid()); - // simulates the glib type system to reuse the plugin. + // simulates the GLib type system to reuse the plugin. TypePluginExt::use_(&plugin); // checks types of object subclasses and of object interfaces registered as dynamic types are still valid. @@ -600,7 +600,7 @@ pub mod plugin { assert!(imp::MyPluginInterfaceLazy::type_().is_valid()); assert!(imp::MyPluginTypeLazy::type_().is_valid()); - // simulates the glib type system to unuse the plugin. + // simulates the GLib type system to unuse the plugin. TypePluginExt::unuse(&plugin); } } diff --git a/glib/Gir_GObject.toml b/glib/Gir_GObject.toml index 7de9527f73a4..250209663f63 100644 --- a/glib/Gir_GObject.toml +++ b/glib/Gir_GObject.toml @@ -24,6 +24,7 @@ manual = [ "GLib.Quark", "GObject.Object", "GObject.Value", + "GObject.EnumValue", "GObject.TypeValueTable", "GObject.ParamFlags", "GObject.ParamSpec", diff --git a/glib/src/enums.rs b/glib/src/enums.rs index 2c59236eea62..dd8768ad60b1 100644 --- a/glib/src/enums.rs +++ b/glib/src/enums.rs @@ -1,11 +1,11 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use std::{cmp, ffi::CStr, fmt, ptr}; +use std::{cmp, ffi::CStr, fmt, ops::Deref, ptr}; use crate::{ translate::*, value::{FromValue, ValueTypeChecker}, - HasParamSpec, ParamSpecEnum, ParamSpecFlags, StaticType, Type, Value, + HasParamSpec, ParamSpecEnum, ParamSpecFlags, StaticType, Type, TypeInfo, Value, }; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] @@ -187,6 +187,39 @@ impl EnumClass { pub fn to_value_by_nick(&self, nick: &str) -> Option { self.value_by_nick(nick).map(|v| v.to_value(self)) } + + // rustdoc-stripper-ignore-next + /// Complete `TypeInfo` for an enum with values. + /// This is an associated function. A method would result in a stack overflow due to a recurvice call: + /// callers should first create an `EnumClass` instance by calling `EnumClass::with_type()` which indirectly + /// calls `TypePluginRegisterImpl::register_dynamic_enum()` and `TypePluginImpl::complete_type_info()` + /// and one of them should call `EnumClass::with_type()` before calling this method. + /// `const_static_values` is a reference on a wrapper of a slice of `EnumValue`. + /// It must be static to ensure enumeration values are never dropped, and ensures that slice is terminated + /// by an `EnumValue` with all members being 0, as expected by GLib. + #[doc(alias = "g_enum_complete_type_info")] + pub fn complete_type_info( + type_: Type, + const_static_values: &'static EnumValues, + ) -> Option { + unsafe { + let is_enum: bool = from_glib(gobject_ffi::g_type_is_a( + type_.into_glib(), + gobject_ffi::G_TYPE_ENUM, + )); + if !is_enum { + return None; + } + + let info = TypeInfo::default(); + gobject_ffi::g_enum_complete_type_info( + type_.into_glib(), + info.as_ptr(), + const_static_values.to_glib_none().0, + ); + Some(info) + } + } } impl Drop for EnumClass { @@ -227,6 +260,15 @@ impl fmt::Debug for EnumValue { } impl EnumValue { + // rustdoc-stripper-ignore-next + /// # Safety + /// + /// It is the responsibility of the caller to ensure `GEnumValue` is + /// valid. + pub const unsafe fn unsafe_from(g_value: gobject_ffi::GEnumValue) -> Self { + Self(g_value) + } + // rustdoc-stripper-ignore-next /// Get integer value corresponding to the value. #[doc(alias = "get_value")] @@ -290,6 +332,12 @@ impl Ord for EnumValue { } } +impl UnsafeFrom for EnumValue { + unsafe fn unsafe_from(g_value: gobject_ffi::GEnumValue) -> Self { + Self::unsafe_from(g_value) + } +} + unsafe impl<'a, 'b> FromValue<'a> for &'b EnumValue { type Checker = EnumTypeChecker; @@ -300,6 +348,78 @@ unsafe impl<'a, 'b> FromValue<'a> for &'b EnumValue { } } +#[doc(hidden)] +impl<'a> ToGlibContainerFromSlice<'a, *const gobject_ffi::GEnumValue> for EnumValue { + type Storage = &'a [Self]; + fn to_glib_none_from_slice(t: &'a [Self]) -> (*const gobject_ffi::GEnumValue, Self::Storage) { + (t.as_ptr() as *const gobject_ffi::GEnumValue, t) + } + fn to_glib_container_from_slice( + _: &'a [Self], + ) -> (*const gobject_ffi::GEnumValue, Self::Storage) { + unimplemented!(); + } + fn to_glib_full_from_slice(_: &[Self]) -> *const gobject_ffi::GEnumValue { + unimplemented!(); + } +} + +// rustdoc-stripper-ignore-next +/// Storage of enumeration values terminated by an `EnumValue` with all members +/// being 0. Should be used only as a storage location for enumeration values +/// when registering an enumeration as a dynamic type. +/// see `TypePluginRegisterImpl::register_dynamic_enum()` and `TypePluginImpl::complete_type_info()`. +/// Inner is intentionally private to ensure other modules will not access the +/// enumeration values by this way. +/// Use `EnumClass::values()` or `EnumClass::value()` to get enumeration values. +#[repr(transparent)] +pub struct EnumValuesStorage([EnumValue; S]); + +impl EnumValuesStorage { + // rustdoc-stripper-ignore-next + pub const fn new(values: [EnumValue; N]) -> Self { + const ZERO: EnumValue = unsafe { + EnumValue::unsafe_from(gobject_ffi::GEnumValue { + value: 0, + value_name: ptr::null(), + value_nick: ptr::null(), + }) + }; + unsafe { + let v: [EnumValue; S] = [ZERO; S]; + ptr::copy_nonoverlapping(values.as_ptr(), v.as_ptr() as _, N); + Self(v) + } + } +} + +impl AsRef for EnumValuesStorage { + fn as_ref(&self) -> &EnumValues { + // SAFETY: EnumValues is repr(transparent) over [EnumValue] so the cast is safe. + unsafe { &*(&self.0 as *const [EnumValue] as *const EnumValues) } + } +} + +// rustdoc-stripper-ignore-next +/// Representation of enumeration values wrapped by `EnumValuesStorage`. Easier +/// to use because don't have a size parameter to be specify. Should be used +/// only to register an enumeration as a dynamic type. +/// see `TypePluginRegisterImpl::register_dynamic_enum()` and `TypePluginImpl::complete_type_info()`. +/// Field is intentionally private to ensure other modules will not access the +/// enumeration values by this way. +/// Use `EnumClass::values()` or `EnumClass::value()` to get the enumeration values. +#[repr(transparent)] +pub struct EnumValues([EnumValue]); + +impl Deref for EnumValues { + type Target = [EnumValue]; + + fn deref(&self) -> &Self::Target { + // SAFETY: EnumValues contains at least the zero `EnumValue` which terminates the enumeration values. + unsafe { std::slice::from_raw_parts(self.0.as_ptr(), self.0.len() - 1) } + } +} + pub struct EnumTypeChecker(); unsafe impl ValueTypeChecker for EnumTypeChecker { type Error = InvalidEnumError; diff --git a/glib/src/gobject/dynamic_object.rs b/glib/src/gobject/dynamic_object.rs index e8f3cc4f06c6..706ddd07ec7a 100644 --- a/glib/src/gobject/dynamic_object.rs +++ b/glib/src/gobject/dynamic_object.rs @@ -1,8 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. use crate::{ - prelude::*, subclass::prelude::*, InterfaceInfo, IsA, TypeFlags, TypeInfo, TypeModule, - TypePlugin, + enums::EnumValues, prelude::*, subclass::prelude::*, InterfaceInfo, IsA, TypeFlags, TypeInfo, + TypeModule, TypePlugin, }; mod sealed { @@ -18,6 +18,12 @@ pub trait DynamicObjectRegisterExt: AsRef + sealed::Sealed + 'static interface_info: &InterfaceInfo, ); + fn register_dynamic_enum( + &self, + name: &str, + const_static_values: &'static EnumValues, + ) -> crate::types::Type; + fn register_dynamic_type( &self, parent_type: crate::types::Type, @@ -41,6 +47,14 @@ where .add_dynamic_interface(instance_type, interface_type, interface_info); } + fn register_dynamic_enum( + &self, + name: &str, + const_static_values: &'static EnumValues, + ) -> crate::types::Type { + self.imp().register_dynamic_enum(name, const_static_values) + } + fn register_dynamic_type( &self, parent_type: crate::types::Type, @@ -63,6 +77,14 @@ impl DynamicObjectRegisterExt for TypeModule { ::add_interface(self, instance_type, interface_type, interface_info); } + fn register_dynamic_enum( + &self, + name: &str, + const_static_values: &'static EnumValues, + ) -> crate::types::Type { + ::register_enum(self, name, const_static_values) + } + fn register_dynamic_type( &self, parent_type: crate::types::Type, diff --git a/glib/src/gobject/type_module.rs b/glib/src/gobject/type_module.rs index 6181d33137bb..e96bf17e0198 100644 --- a/glib/src/gobject/type_module.rs +++ b/glib/src/gobject/type_module.rs @@ -1,6 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{prelude::*, translate::*, InterfaceInfo, TypeFlags, TypeInfo, TypePlugin}; +use crate::{ + enums::EnumValues, prelude::*, translate::*, InterfaceInfo, TypeFlags, TypeInfo, TypePlugin, +}; crate::wrapper! { #[doc(alias = "GTypeModule")] @@ -38,6 +40,21 @@ pub trait TypeModuleExt: IsA + sealed::Sealed + 'static { } } + #[doc(alias = "g_type_module_register_enum")] + fn register_enum( + &self, + name: &str, + const_static_values: &'static EnumValues, + ) -> crate::types::Type { + unsafe { + from_glib(gobject_ffi::g_type_module_register_enum( + self.as_ref().to_glib_none().0, + name.to_glib_none().0, + const_static_values.to_glib_none().0, + )) + } + } + #[doc(alias = "g_type_module_register_type")] fn register_type( &self, diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 5e5e470861de..72fe445a61b9 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -11,8 +11,8 @@ pub use ffi; pub use glib_macros::cstr_bytes; pub use glib_macros::{ clone, closure, closure_local, derived_properties, dynamic_object_interface, - dynamic_object_subclass, flags, object_interface, object_subclass, Boxed, Downgrade, Enum, - ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, + dynamic_object_subclass, flags, object_interface, object_subclass, Boxed, Downgrade, + DynamicEnum, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, }; pub use gobject_ffi; pub use once_cell; @@ -140,7 +140,7 @@ mod checksum; pub mod closure; mod convert; pub use self::convert::*; -mod enums; +pub mod enums; mod functions; pub use self::functions::*; mod key_file; diff --git a/glib/src/subclass/mod.rs b/glib/src/subclass/mod.rs index 271a4a98c81e..6ada58559ce7 100644 --- a/glib/src/subclass/mod.rs +++ b/glib/src/subclass/mod.rs @@ -275,7 +275,7 @@ //! let simple_module_object_type = imp::SimpleModuleObject::type_(); //! assert!(!simple_module_object_type.is_valid()); //! -//! // simulates the glib type system to load the module. +//! // simulates the GLib type system to load the module. //! TypeModuleExt::use_(&simple_type_module); //! //! // at this step, SimpleModuleObject must have been registered. @@ -343,10 +343,6 @@ //! } //! //! impl TypePluginRegisterImpl for SimpleTypePlugin { -//! fn add_dynamic_interface(&self, _: glib::Type, _: glib::Type, _: &glib::InterfaceInfo) { -//! unimplemented!() -//! } -//! //! fn register_dynamic_type(&self, parent_type: glib::Type, type_name: &str, type_info: &glib::TypeInfo, flags: glib::TypeFlags) -> glib::Type { //! let type_ = glib::Type::from_name(type_name).unwrap_or_else(|| { //! glib::Type::register_dynamic(parent_type, type_name, self.obj().upcast_ref::(), flags) @@ -385,7 +381,7 @@ //! let simple_plugin_object_type = imp::SimplePluginObject::type_(); //! assert!(!simple_plugin_object_type.is_valid()); //! -//! // simulates the glib type system to use the plugin. +//! // simulates the GLib type system to use the plugin. //! TypePluginExt::use_(&simple_type_plugin); //! //! // at this step, SimplePluginObject must have been registered. diff --git a/glib/src/subclass/type_module.rs b/glib/src/subclass/type_module.rs index bef50c6ad7a4..b8009758c716 100644 --- a/glib/src/subclass/type_module.rs +++ b/glib/src/subclass/type_module.rs @@ -156,7 +156,7 @@ mod tests { fn test_module() { assert!(!imp::SimpleModuleType::type_().is_valid()); let simple_module = glib::Object::new::(); - // simulates the glib type system to load the module. + // simulates the GLib type system to load the module. assert!(simple_module.use_()); assert!(imp::SimpleModuleType::type_().is_valid()); simple_module.unuse(); diff --git a/glib/src/subclass/type_plugin.rs b/glib/src/subclass/type_plugin.rs index cda3581bb97e..6116c9bcc9fd 100644 --- a/glib/src/subclass/type_plugin.rs +++ b/glib/src/subclass/type_plugin.rs @@ -1,10 +1,10 @@ // Take a look at the license at the top of the repository in the LICENSE file. +use crate::enums::EnumValues; use crate::translate::IntoGlib; use crate::translate::{FromGlib, ToGlibPtr}; -use crate::TypeFlags; use crate::{ - subclass::prelude::*, Cast, Interface, InterfaceInfo, Type, TypeInfo, TypePlugin, + subclass::prelude::*, Cast, Interface, InterfaceInfo, Type, TypeFlags, TypeInfo, TypePlugin, TypeValueTable, }; @@ -183,15 +183,25 @@ pub trait TypePluginRegisterImpl: ObjectImpl + TypePluginImpl { _instance_type: Type, _interface_type: Type, _interface_info: &InterfaceInfo, - ); - + ) { + unimplemented!() + } + fn register_dynamic_enum( + &self, + _name: &str, + _const_static_values: &'static EnumValues, + ) -> Type { + unimplemented!() + } fn register_dynamic_type( &self, _parent_type: Type, _type_name: &str, _type_info: &TypeInfo, _flags: TypeFlags, - ) -> Type; + ) -> Type { + unimplemented!() + } } #[cfg(test)] @@ -236,15 +246,6 @@ mod tests { } impl TypePluginRegisterImpl for SimplePlugin { - fn add_dynamic_interface( - &self, - _instance_type: Type, - _interface_type: Type, - _interface_info: &InterfaceInfo, - ) { - unimplemented!() - } - fn register_dynamic_type( &self, parent_type: Type, @@ -293,7 +294,7 @@ mod tests { fn test_plugin() { assert!(!imp::SimplePluginType::type_().is_valid()); let simple_plugin = crate::Object::new::(); - // simulates the glib type system to use the plugin. + // simulates the GLib type system to use the plugin. TypePluginExt::use_(&simple_plugin); assert!(imp::SimplePluginType::type_().is_valid()); TypePluginExt::unuse(&simple_plugin);