From 6acaf63ea3c44d5d3e52cde4993a2e646f8f229c Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 5 Aug 2023 15:19:49 +0200 Subject: [PATCH 1/3] Consistency (mostly `::godot` in macros) --- godot-core/src/builtin/real.rs | 2 +- godot-core/src/engine.rs | 2 +- godot-macros/src/derive_from_variant.rs | 59 +++++++++------- godot-macros/src/derive_godot_class/mod.rs | 2 +- godot-macros/src/derive_to_variant.rs | 30 ++++---- godot-macros/src/godot_api.rs | 22 +++--- godot-macros/src/lib.rs | 70 +++++++++---------- godot-macros/src/method_registration/mod.rs | 8 +-- .../method_registration/register_method.rs | 14 ++-- .../virtual_method_callback.rs | 8 +-- godot-macros/src/util/mod.rs | 38 +++++----- 11 files changed, 133 insertions(+), 122 deletions(-) diff --git a/godot-core/src/builtin/real.rs b/godot-core/src/builtin/real.rs index 4e1ae68f9..416fc46e6 100644 --- a/godot-core/src/builtin/real.rs +++ b/godot-core/src/builtin/real.rs @@ -199,7 +199,7 @@ macro_rules! real { /// Array of reals. /// -/// ### Examples +/// # Example /// ``` /// use godot_core::builtin::{real, reals}; /// diff --git a/godot-core/src/engine.rs b/godot-core/src/engine.rs index 5695b29ea..d70554c59 100644 --- a/godot-core/src/engine.rs +++ b/godot-core/src/engine.rs @@ -160,7 +160,7 @@ where /// /// The path must be absolute (typically starting with `res://`), a local path will fail. /// -/// # Example: +/// # Example /// Loads a scene called `Main` located in the `path/to` subdirectory of the Godot project and caches it in a variable. /// The resource is directly stored with type `PackedScene`. /// diff --git a/godot-macros/src/derive_from_variant.rs b/godot-macros/src/derive_from_variant.rs index 3afe4783e..8110a01b6 100644 --- a/godot-macros/src/derive_from_variant.rs +++ b/godot-macros/src/derive_from_variant.rs @@ -21,24 +21,26 @@ pub fn transform(decl: Declaration) -> ParseResult { name, name_string, } = decl_get_info(&decl); + let mut body = quote! { - let root = variant.try_to::()?; - let root = root.get(#name_string).ok_or(godot::builtin::VariantConversionError::BadType)?; + let root = variant.try_to::<::godot::builtin::Dictionary>()?; + let root = root.get(#name_string).ok_or(::godot::builtin::VariantConversionError::BadType)?; }; match decl { Declaration::Struct(s) => match s.fields { - venial::StructFields::Unit => make_unit_struct(&mut body), - venial::StructFields::Tuple(fields) if fields.fields.len() == 1 => { + StructFields::Unit => make_unit_struct(&mut body), + StructFields::Tuple(fields) if fields.fields.len() == 1 => { make_new_type_struct(&mut body, fields) } - venial::StructFields::Tuple(fields) => make_tuple_struct(fields, &mut body, &name), - venial::StructFields::Named(fields) => make_named_struct(fields, &mut body, &name), + StructFields::Tuple(fields) => make_tuple_struct(fields, &mut body, &name), + StructFields::Named(fields) => make_named_struct(fields, &mut body, &name), }, Declaration::Enum(enum_) => { if enum_.variants.is_empty() { + // Uninhabited enums have no values, so we cannot convert an actual Variant into them. body = quote! { - panic!(); + panic!("cannot convert Variant into uninhabited enum {}", #name_string); } } else { let mut matches = quote! {}; @@ -89,19 +91,21 @@ pub fn transform(decl: Declaration) -> ParseResult { body = quote! { #body #matches - Err(godot::builtin::VariantConversionError::MissingValue) + Err(::godot::builtin::VariantConversionError::MissingValue) }; } } + + // decl_get_info() above ensured that no other cases are possible. _ => unreachable!(), } let gen = generic_params.as_ref().map(|x| x.as_inline_args()); Ok(quote! { - impl #generic_params godot::builtin::FromVariant for #name #gen #where_ { + impl #generic_params ::godot::builtin::FromVariant for #name #gen #where_ { fn try_from_variant( - variant: &godot::builtin::Variant - ) -> Result { + variant: &::godot::builtin::Variant + ) -> Result { #body } } @@ -123,7 +127,7 @@ fn make_named_struct( ( quote! { let #ident = root.get(#string_ident) - .ok_or(godot::builtin::VariantConversionError::MissingValue)?; + .ok_or(::godot::builtin::VariantConversionError::MissingValue)?; }, quote! { #ident: #ident.try_to()? }, ) @@ -132,7 +136,7 @@ fn make_named_struct( let (set_idents, set_self): (Vec<_>, Vec<_>) = fields.unzip(); *body = quote! { #body - let root = root.try_to::()?; + let root = root.try_to::<::godot::builtin::Dictionary>()?; #( #set_idents )* @@ -157,7 +161,7 @@ fn make_tuple_struct( } else { quote! { let #ident = root.pop_front() - .ok_or(godot::builtin::VariantConversionError::MissingValue)? + .ok_or(::godot::builtin::VariantConversionError::MissingValue)? .try_to::<#field_type>()?; } }, @@ -166,7 +170,7 @@ fn make_tuple_struct( let (idents, ident_set): (Vec<_>, Vec<_>) = ident_and_set.unzip(); *body = quote! { #body - let mut root = root.try_to::()?; + let mut root = root.try_to::<::godot::builtin::VariantArray>()?; #( #ident_set )* @@ -202,7 +206,7 @@ fn make_enum_new_type( ) -> TokenStream { let field_type = &field.ty; quote! { - if let Ok(child) = root.try_to::() { + if let Ok(child) = root.try_to::<::godot::builtin::Dictionary>() { if let Some(variant) = child.get(#variant_name_string) { return Ok(Self::#variant_name(variant.try_to::<#field_type>()?)); } @@ -217,7 +221,7 @@ fn make_enum_new_type_skipped( ) -> TokenStream { let field_type = &field.ty; quote! { - if let Ok(child) = root.try_to::() { + if let Ok(child) = root.try_to::<::godot::builtin::Dictionary>() { if let Some(v) = child.get(#variant_name_string) { if v.is_nil() { return Ok(Self::#variant_name( @@ -244,7 +248,7 @@ fn make_enum_tuple( } else { quote! { let #ident = variant.pop_front() - .ok_or(godot::builtin::VariantConversionError::MissingValue)? + .ok_or(::godot::builtin::VariantConversionError::MissingValue)? .try_to::<#field_type>()?; } }; @@ -253,10 +257,10 @@ fn make_enum_tuple( let (idents, set_idents): (Vec<_>, Vec<_>) = fields.unzip(); quote! { - let child = root.try_to::(); + let child = root.try_to::<::godot::builtin::Dictionary>(); if let Ok(child) = child { if let Some(variant) = child.get(#variant_name_string) { - let mut variant = variant.try_to::()?; + let mut variant = variant.try_to::<::godot::builtin::VariantArray>()?; #(#set_idents)* return Ok(Self::#variant_name(#(#idents ,)*)); } @@ -272,28 +276,31 @@ fn make_enum_named( let field_name = &field.name; let field_name_string = &field.name.to_string(); let field_type = &field.ty; - let set_field = if has_attr(&field.attributes,"variant","skip") { + let set_field = if has_attr(&field.attributes, "variant", "skip") { quote! { let #field_name = <#field_type as Default>::default(); } } else { quote! { let #field_name = variant.get(#field_name_string) - .ok_or(godot::builtin::VariantConversionError::MissingValue)? - .try_to::<#field_type>()?; + .ok_or(::godot::builtin::VariantConversionError::MissingValue)? + .try_to::<#field_type>()?; } }; (field_name.to_token_stream(), set_field) }); + let (fields, set_fields): (Vec<_>, Vec<_>) = fields.unzip(); quote! { - if let Ok(root) = root.try_to::() { + if let Ok(root) = root.try_to::<::godot::builtin::Dictionary>() { if let Some(variant) = root.get(#variant_name_string) { - let variant = variant.try_to::()?; + let variant = variant.try_to::<::godot::builtin::Dictionary>()?; #( #set_fields )* - return Ok(Self::#variant_name{ #(#fields,)* }); + return Ok(Self::#variant_name { + #( #fields, )* + }); } } } diff --git a/godot-macros/src/derive_godot_class/mod.rs b/godot-macros/src/derive_godot_class/mod.rs index 78bddff6b..d57088f95 100644 --- a/godot-macros/src/derive_godot_class/mod.rs +++ b/godot-macros/src/derive_godot_class/mod.rs @@ -210,7 +210,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { let rest_init = fields.all_fields.into_iter().map(|field| { let field_name = field.name; let value_expr = match field.default { - None => quote!(::std::default::Default::default()), + None => quote! { ::std::default::Default::default() }, Some(default) => default, }; quote! { #field_name: #value_expr, } diff --git a/godot-macros/src/derive_to_variant.rs b/godot-macros/src/derive_to_variant.rs index 784d5c307..aa8ecb1eb 100644 --- a/godot-macros/src/derive_to_variant.rs +++ b/godot-macros/src/derive_to_variant.rs @@ -14,7 +14,7 @@ use venial::{Declaration, StructFields}; pub fn transform(decl: Declaration) -> ParseResult { let mut body = quote! { - let mut root = godot::builtin::Dictionary::new(); + let mut root = ::godot::builtin::Dictionary::new(); }; let DeclInfo { @@ -43,8 +43,8 @@ pub fn transform(decl: Declaration) -> ParseResult { }; let arm_content = match &enum_v.contents { _ if has_attr(&enum_v.attributes, "variant", "skip") => quote! { - return godot::builtin::dict!{ - #name_string : godot::builtin::Variant::nil() + return ::godot::builtin::dict! { + #name_string: ::godot::builtin::Variant::nil() }.to_variant(); }, StructFields::Unit => quote! { #variant_name_string.to_variant() }, @@ -69,31 +69,32 @@ pub fn transform(decl: Declaration) -> ParseResult { root.insert(#name_string, content); }; } - // This is unreachable cause this case has already returned - // with an error in decl_get_info call. + + // decl_get_info() above ensured that no other cases are possible. _ => unreachable!(), }; + body = quote! { #body root.to_variant() }; let gen = generic_params.as_ref().map(|x| x.as_inline_args()); - // we need to allow unreachable for Uninhabited enums because it uses match self {} - // it's okay since we can't ever have a value to call to_variant on it anyway. - let allow_unreachable = matches!(&decl,Declaration::Enum(e) if e.variants.is_empty()); - let allow_unreachable = if allow_unreachable { + + // We need to allow unreachable code for uninhabited enums, because it uses match self {}. + // This is safe, since we can't ever have a value to call to_variant on it anyway. + let allow_unreachable = if matches!(&decl, Declaration::Enum(e) if e.variants.is_empty()) { quote! { #[allow(unreachable_code)] } } else { - quote! {} + TokenStream::new() }; Ok(quote! { - impl #generic_params godot::builtin::ToVariant for #name #gen #where_ { + impl #generic_params ::godot::builtin::ToVariant for #name #gen #where_ { #allow_unreachable - fn to_variant(&self) -> godot::builtin::Variant { + fn to_variant(&self) -> ::godot::builtin::Variant { #body } } @@ -140,12 +141,13 @@ fn make_enum_named_arm( root.insert(#ident_string,#ident.to_variant()); } }); + quote! { - let mut root = godot::builtin::Dictionary::new(); + let mut root = ::godot::builtin::Dictionary::new(); #( #fields )* - godot::builtin::dict!{ #variant_name_string : root }.to_variant() + ::godot::builtin::dict! { #variant_name_string: root }.to_variant() } } diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index 799af605a..22f6130f4 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -4,8 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::method_registration::gdext_virtual_method_callback; -use crate::method_registration::make_method_registration; +use crate::method_registration::{make_method_registration, make_virtual_method_callback}; use crate::util; use crate::util::bail; use proc_macro2::{Ident, TokenStream}; @@ -246,7 +245,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), } BoundAttrType::Const(_) => { return attr.bail( - "#[constant] can only be used on associated cosntant", + "#[constant] can only be used on associated constant", method, ) } @@ -386,9 +385,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result { } }; - register_fn = quote! { Some(#prv::ErasedRegisterFn { - raw: #prv::callbacks::register_class_by_builder::<#class_name> - }) }; + register_fn = quote! { + Some(#prv::ErasedRegisterFn { + raw: #prv::callbacks::register_class_by_builder::<#class_name> + }) + }; } "init" => { @@ -452,7 +453,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result { let virtual_method_callbacks: Vec = virtual_methods .iter() - .map(|method| gdext_virtual_method_callback(&class_name, method)) + .map(|method| make_virtual_method_callback(&class_name, method)) .collect(); let result = quote! { @@ -470,9 +471,10 @@ fn transform_trait_impl(original_impl: Impl) -> Result { let __config = unsafe { ::godot::sys::config() }; - if !__config.virtuals_active_in_editor - && *__config.is_editor.get_or_init(|| ::godot::engine::Engine::singleton().is_editor_hint()) { - return None; + if __config.tools_only { + && *__config.is_editor.get_or_init(|| ::godot::engine::Engine::singleton().is_editor_hint()) { + return None; + } } match name { diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 56d72ec21..732448c2d 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -27,9 +27,9 @@ /// /// # Examples /// -/// ## Example with `RefCounted` as a base +/// ## `RefCounted` as a base /// -/// ``` +/// ```no_run ///# use godot::prelude::*; /// /// #[derive(GodotClass)] @@ -58,9 +58,9 @@ /// Note that you have to implement init otherwise you won't be able to call new or any /// other methods from GDScript. /// -/// ## Example with `Node` as a Base +/// ## `Node` as a base /// -/// ``` +/// ```no_run ///# use godot::prelude::*; /// /// #[derive(GodotClass)] @@ -389,32 +389,32 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { /// Derive macro for [ToVariant](godot::builtin::ToVariant) on structs or enums. /// -/// Example : +/// # Example /// -/// ```ignore +/// ```no_run +/// # use godot::prelude::*; /// #[derive(FromVariant, ToVariant, PartialEq, Debug)] /// struct StructNamed { /// field1: String, /// field2: i32, /// } /// +/// let obj = StructNamed { +/// field1: "1".to_string(), +/// field2: 2, +/// }; +/// let dict = dict! { +/// "StructNamed": dict! { +/// "field1": "four", +/// "field2": 5, +/// } +/// }; +/// /// // This would not panic. -/// assert!( -/// StructNamed { -/// field1: "1".to_string(), -/// field2: 2, -/// } -/// .to_variant() -/// == dict! { -/// "StructNamed":dict!{ -/// "field1":"four","field2":5 -/// } -/// } -/// .to_variant() -/// ); +/// assert_eq!(obj.to_variant(), dict.to_variant()); /// ``` /// -/// You can use the skip attribute to ignore a field from being converted to ToVariant. +/// You can use the `#[skip]` attribute to ignore a field from being converted to `ToVariant`. #[proc_macro_derive(ToVariant, attributes(variant))] pub fn derive_to_variant(input: TokenStream) -> TokenStream { translate(input, derive_to_variant::transform) @@ -422,29 +422,29 @@ pub fn derive_to_variant(input: TokenStream) -> TokenStream { /// Derive macro for [FromVariant](godot::builtin::FromVariant) on structs or enums. /// -/// Example : +/// # Example /// -/// ```ignore +/// ```no_run +/// # use godot::prelude::*; /// #[derive(FromVariant, ToVariant, PartialEq, Debug)] /// struct StructNamed { /// field1: String, /// field2: i32, /// } /// +/// let obj = StructNamed { +/// field1: "1".to_string(), +/// field2: 2, +/// }; +/// let dict_variant = dict! { +/// "StructNamed": dict! { +/// "field1": "four", +/// "field2": 5, +/// } +/// }.to_variant(); +/// /// // This would not panic. -/// assert!( -/// StructNamed::from_variant( -/// &dict! { -/// "StructNamed":dict!{ -/// "field1":"four","field2":5 -/// } -/// } -/// .to_variant() -/// ) == StructNamed { -/// field1: "1".to_string(), -/// field2: 2, -/// } -/// ); +/// assert_eq!(StructNamed::from_variant(&dict_variant), obj); /// ``` /// /// You can use the skip attribute to ignore a field from the provided variant and use `Default::default()` diff --git a/godot-macros/src/method_registration/mod.rs b/godot-macros/src/method_registration/mod.rs index 84338db93..2bb349687 100644 --- a/godot-macros/src/method_registration/mod.rs +++ b/godot-macros/src/method_registration/mod.rs @@ -10,7 +10,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; pub use register_method::make_method_registration; -pub use virtual_method_callback::gdext_virtual_method_callback; +pub use virtual_method_callback::make_virtual_method_callback; #[derive(Copy, Clone, Eq, PartialEq)] enum ReceiverType { @@ -49,7 +49,7 @@ fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) - let ( #(#params,)* ) = params; let storage = - unsafe { godot::private::as_storage::<#class_name>(instance_ptr) }; + unsafe { ::godot::private::as_storage::<#class_name>(instance_ptr) }; #instance_decl instance.#method_name(#(#params),*) @@ -142,7 +142,7 @@ fn make_ptrcall_invocation( }; quote! { - <#sig_tuple as godot::builtin::meta::PtrcallSignatureTuple>::ptrcall( + <#sig_tuple as ::godot::builtin::meta::PtrcallSignatureTuple>::ptrcall( instance_ptr, args, ret, @@ -162,7 +162,7 @@ fn make_varcall_invocation( let method_name_str = method_name.to_string(); quote! { - <#sig_tuple as godot::builtin::meta::VarcallSignatureTuple>::varcall( + <#sig_tuple as ::godot::builtin::meta::VarcallSignatureTuple>::varcall( instance_ptr, args, ret, diff --git a/godot-macros/src/method_registration/register_method.rs b/godot-macros/src/method_registration/register_method.rs index aa75ac8ca..bf79e64dc 100644 --- a/godot-macros/src/method_registration/register_method.rs +++ b/godot-macros/src/method_registration/register_method.rs @@ -38,10 +38,10 @@ pub fn make_method_registration( quote! { { - use godot::obj::GodotClass; - use godot::builtin::meta::registration::method::MethodInfo; - use godot::builtin::{StringName, Variant}; - use godot::sys; + use ::godot::obj::GodotClass; + use ::godot::builtin::meta::registration::method::MethodInfo; + use ::godot::builtin::{StringName, Variant}; + use ::godot::sys; type Sig = #sig_tuple; @@ -67,7 +67,7 @@ pub fn make_method_registration( ) }; - godot::private::out!( + ::godot::private::out!( " Register fn: {}::{}", #class_name_str, #method_name_str @@ -97,7 +97,7 @@ fn make_varcall_func( ret: sys::GDExtensionVariantPtr, err: *mut sys::GDExtensionCallError, ) { - let success = godot::private::handle_panic( + let success = ::godot::private::handle_panic( || stringify!(#method_name), || #invocation ); @@ -132,7 +132,7 @@ fn make_ptrcall_func( args: *const sys::GDExtensionConstTypePtr, ret: sys::GDExtensionTypePtr, ) { - let success = godot::private::handle_panic( + let success = ::godot::private::handle_panic( || stringify!(#method_name), || #invocation ); diff --git a/godot-macros/src/method_registration/virtual_method_callback.rs b/godot-macros/src/method_registration/virtual_method_callback.rs index 043a18ff0..d4bca8874 100644 --- a/godot-macros/src/method_registration/virtual_method_callback.rs +++ b/godot-macros/src/method_registration/virtual_method_callback.rs @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, TokenStream as TokenStream2}; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use venial::Function; @@ -17,10 +17,10 @@ use crate::util; // // There are currently no virtual static methods. Additionally, virtual static methods dont really make a lot // of sense. Therefore there is no need to support them. -pub fn gdext_virtual_method_callback( +pub fn make_virtual_method_callback( class_name: &Ident, method_signature: &Function, -) -> TokenStream2 { +) -> TokenStream { let signature_info = get_signature_info(method_signature); let method_name = &method_signature.name; @@ -32,7 +32,7 @@ pub fn gdext_virtual_method_callback( quote! { { - use godot::sys; + use ::godot::sys; unsafe extern "C" fn function( instance_ptr: sys::GDExtensionClassInstancePtr, diff --git a/godot-macros/src/util/mod.rs b/godot-macros/src/util/mod.rs index 4fe17146b..390cefbde 100644 --- a/godot-macros/src/util/mod.rs +++ b/godot-macros/src/util/mod.rs @@ -217,29 +217,29 @@ pub(crate) fn path_ends_with(path: &[TokenTree], expected: &str) -> bool { pub(crate) struct DeclInfo { pub where_: Option, pub generic_params: Option, - pub name: proc_macro2::Ident, + pub name: Ident, pub name_string: String, } pub(crate) fn decl_get_info(decl: &venial::Declaration) -> DeclInfo { - let (where_, generic_params, name, name_string) = - if let venial::Declaration::Struct(struct_) = decl { - ( - struct_.where_clause.clone(), - struct_.generic_params.clone(), - struct_.name.clone(), - struct_.name.to_string(), - ) - } else if let venial::Declaration::Enum(enum_) = decl { - ( - enum_.where_clause.clone(), - enum_.generic_params.clone(), - enum_.name.clone(), - enum_.name.to_string(), - ) - } else { - panic!("only enums and structs are supported at the moment.") - }; + let (where_, generic_params, name, name_string) = match decl { + venial::Declaration::Struct(struct_) => ( + struct_.where_clause.clone(), + struct_.generic_params.clone(), + struct_.name.clone(), + struct_.name.to_string(), + ), + venial::Declaration::Enum(enum_) => ( + enum_.where_clause.clone(), + enum_.generic_params.clone(), + enum_.name.clone(), + enum_.name.to_string(), + ), + _ => { + panic!("only enums and structs are supported at the moment") + } + }; + DeclInfo { where_, generic_params, From 23e973fbde34e1d1d21eb149d149a86646f2521e Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 6 Aug 2023 17:02:23 +0200 Subject: [PATCH 2/3] #[class(tool)] allows for per-class enabling in the editor More fine-grained than previous all-or-nothing approach. --- godot-core/src/init/mod.rs | 25 ++++++++++++---------- godot-core/src/lib.rs | 17 +++++++++++++++ godot-core/src/registry.rs | 4 ++++ godot-ffi/src/lib.rs | 2 +- godot-macros/src/derive_godot_class/mod.rs | 24 +++++++++++++++++++++ godot-macros/src/godot_api.rs | 8 ++----- godot-macros/src/lib.rs | 15 ++++++++++++- 7 files changed, 76 insertions(+), 19 deletions(-) diff --git a/godot-core/src/init/mod.rs b/godot-core/src/init/mod.rs index b068e8aa6..1c115936d 100644 --- a/godot-core/src/init/mod.rs +++ b/godot-core/src/init/mod.rs @@ -18,13 +18,13 @@ pub unsafe fn __gdext_load_library( init: *mut sys::GDExtensionInitialization, ) -> sys::GDExtensionBool { let init_code = || { - let virtuals_active_in_editor = match E::editor_run_behavior() { - EditorRunBehavior::Full => true, - EditorRunBehavior::NoVirtuals => false, + let tool_only_in_editor = match E::editor_run_behavior() { + EditorRunBehavior::ToolClassesOnly => true, + EditorRunBehavior::AllClasses => false, }; let config = sys::GdextConfig { - virtuals_active_in_editor, + tool_only_in_editor, is_editor: cell::OnceCell::new(), }; @@ -132,7 +132,7 @@ pub unsafe trait ExtensionLibrary { /// Determines if and how an extension's code is run in the editor. fn editor_run_behavior() -> EditorRunBehavior { - EditorRunBehavior::NoVirtuals + EditorRunBehavior::ToolClassesOnly } } @@ -144,19 +144,22 @@ pub unsafe trait ExtensionLibrary { /// /// In many cases, users write extension code with the intention to run in games, not inside the editor. /// This is why the default behavior in gdext deviates from Godot: lifecycle callbacks are disabled inside the -/// editor. It is however possible to restore the original behavior with this enum. +/// editor (see [`ToolClassesOnly`][Self::ToolClassesOnly]). It is possible to configure this. /// /// See also [`ExtensionLibrary::editor_run_behavior()`]. #[derive(Copy, Clone, Debug)] #[non_exhaustive] pub enum EditorRunBehavior { - /// Runs the extension with full functionality in editor. - Full, + /// Only runs `#[class(tool)]` classes in the editor. + /// + /// All classes are registered, and calls from GDScript to Rust are possible. However, virtual lifecycle callbacks + /// (`_ready`, `_process`, `_physics_process`, ...) are not run unless the class is annotated with `#[class(tool)]`. + ToolClassesOnly, - /// Does not invoke any Godot virtual functions. + /// Runs the extension with full functionality in editor. /// - /// Classes are still registered, and calls from GDScript to Rust are still possible. - NoVirtuals, + /// Ignores any `#[class(tool)]` annotations. + AllClasses, } pub trait ExtensionLayer: 'static { diff --git a/godot-core/src/lib.rs b/godot-core/src/lib.rs index 1b71dfa4e..8e7124f74 100644 --- a/godot-core/src/lib.rs +++ b/godot-core/src/lib.rs @@ -63,6 +63,23 @@ pub mod private { sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor); } + pub struct ClassConfig { + pub is_tool: bool, + } + + pub fn is_class_inactive(is_tool: bool) -> bool { + if is_tool { + return false; + } + + // SAFETY: only invoked after global library initialization. + let global_config = unsafe { sys::config() }; + let is_editor = || crate::engine::Engine::singleton().is_editor_hint(); + + global_config.tool_only_in_editor //. + && *global_config.is_editor.get_or_init(is_editor) + } + fn print_panic(err: Box) { if let Some(s) = err.downcast_ref::<&'static str>() { print_panic_message(s); diff --git a/godot-core/src/registry.rs b/godot-core/src/registry.rs index 0ae362e10..44aced7ec 100644 --- a/godot-core/src/registry.rs +++ b/godot-core/src/registry.rs @@ -20,6 +20,10 @@ use std::any::Any; use std::collections::HashMap; use std::{fmt, ptr}; +// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginComponent, while others is directly +// translated to code. Consider moving more code to the PluginComponent, which allows for more dynamic registration and will +// be easier for a future builder API. + /// Piece of information that is gathered by the self-registration ("plugin") system. #[derive(Debug)] pub struct ClassPlugin { diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index f5fb9f3e5..1594d7007 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -61,7 +61,7 @@ struct GdextRuntimeMetadata { } pub struct GdextConfig { - pub virtuals_active_in_editor: bool, + pub tool_only_in_editor: bool, pub is_editor: cell::OnceCell, } diff --git a/godot-macros/src/derive_godot_class/mod.rs b/godot-macros/src/derive_godot_class/mod.rs index d57088f95..1c1c82adf 100644 --- a/godot-macros/src/derive_godot_class/mod.rs +++ b/godot-macros/src/derive_godot_class/mod.rs @@ -46,6 +46,8 @@ pub fn transform(decl: Declaration) -> ParseResult { create_fn = quote! { None }; }; + let config_impl = make_config_impl(class_name, struct_cfg.is_tool); + Ok(quote! { unsafe impl ::godot::obj::GodotClass for #class_name { type Base = #base_class; @@ -59,6 +61,7 @@ pub fn transform(decl: Declaration) -> ParseResult { #godot_init_impl #godot_exports_impl + #config_impl ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin { class_name: #class_name_obj, @@ -77,6 +80,7 @@ pub fn transform(decl: Declaration) -> ParseResult { fn parse_struct_attributes(class: &Struct) -> ParseResult { let mut base_ty = ident("RefCounted"); let mut has_generated_init = false; + let mut is_tool = false; // #[class] attribute on struct if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? { @@ -88,12 +92,17 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult { has_generated_init = true; } + if parser.handle_alone("tool")? { + is_tool = true; + } + parser.finish()?; } Ok(ClassAttributes { base_ty, has_generated_init, + is_tool, }) } @@ -144,6 +153,7 @@ fn parse_fields(class: &Struct) -> ParseResult { field.export = Some(export); parser.finish()?; } + // #[var] if let Some(mut parser) = KvParser::parse(&named_field.attributes, "var")? { let var = FieldVar::new_from_kv(&mut parser)?; @@ -171,6 +181,7 @@ fn parse_fields(class: &Struct) -> ParseResult { struct ClassAttributes { base_ty: Ident, has_generated_init: bool, + is_tool: bool, } struct Fields { @@ -228,6 +239,19 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { } } +fn make_config_impl(class_name: &Ident, is_tool: bool) -> TokenStream { + quote! { + impl #class_name { + #[doc(hidden)] + pub fn __config() -> ::godot::private::ClassConfig { + ::godot::private::ClassConfig { + is_tool: #is_tool, + } + } + } + } +} + /// Checks at compile time that a function with the given name exists on `Self`. #[must_use] fn make_existence_check(ident: &Ident) -> TokenStream { diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index 22f6130f4..8590c29ad 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -469,12 +469,8 @@ fn transform_trait_impl(original_impl: Impl) -> Result { fn __virtual_call(name: &str) -> ::godot::sys::GDExtensionClassCallVirtual { //println!("virtual_call: {}.{}", std::any::type_name::(), name); - let __config = unsafe { ::godot::sys::config() }; - - if __config.tools_only { - && *__config.is_editor.get_or_init(|| ::godot::engine::Engine::singleton().is_editor_hint()) { - return None; - } + if ::godot::private::is_class_inactive(Self::__config().is_tool) { + return None; } match name { diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 732448c2d..32da8da6e 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -100,6 +100,7 @@ mod util; /// Derive macro for [the `GodotClass` trait](../obj/trait.GodotClass.html) on structs. You must use this /// macro; manual implementations of the `GodotClass` trait are not supported. /// +/// /// # Construction /// /// To generate a constructor that will let you call `MyStruct.new()` from GDScript, annotate your @@ -180,7 +181,8 @@ mod util; /// } /// ``` /// -/// # Exported properties +/// +/// # Properties and exports /// /// In GDScript, there is a distinction between /// [properties](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#properties-setters-and-getters) @@ -382,6 +384,17 @@ mod util; /// /// The `#[signal]` attribute is accepted, but not yet implemented. See [issue /// #8](https://github.com/godot-rust/gdext/issues/8). +/// +/// +/// # Running code in the editor +/// +/// If you annotate a class with `#[class(tool)]`, its lifecycle methods (`ready()`, `process()` etc.) will be invoked in the editor. This +/// is useful for writing custom editor plugins, as opposed to classes running simply in-game. +/// +/// See [`ExtensionLibrary::editor_run_behavior()`](../init/trait.ExtensionLibrary.html#method.editor_run_behavior) +/// for more information and further customization. +/// +/// This is very similar to [GDScript's `@tool` feature](https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html). #[proc_macro_derive(GodotClass, attributes(class, base, var, export, init, signal))] pub fn derive_native_class(input: TokenStream) -> TokenStream { translate(input, derive_godot_class::transform) From 55922ebaaf9a49b9d87c790a293dfb384696e10c Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 6 Aug 2023 17:02:52 +0200 Subject: [PATCH 3/3] Skip editor processing also inside on_notification() --- godot-macros/src/godot_api.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index 8590c29ad..c540a4e8b 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -419,6 +419,10 @@ fn transform_trait_impl(original_impl: Impl) -> Result { on_notification_impl = quote! { impl ::godot::obj::cap::GodotNotification for #class_name { fn __godot_notification(&mut self, what: i32) { + if ::godot::private::is_class_inactive(Self::__config().is_tool) { + return; + } + ::on_notification(self, what.into()) } }