Skip to content

Commit

Permalink
Merge pull request #374 from godot-rust/feature/tool
Browse files Browse the repository at this point in the history
`#[class(tool)]` for per-class editor run behavior
  • Loading branch information
Bromeon authored Aug 6, 2023
2 parents 7a44ea8 + 55922eb commit 720824a
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 136 deletions.
2 changes: 1 addition & 1 deletion godot-core/src/builtin/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ macro_rules! real {

/// Array of reals.
///
/// ### Examples
/// # Example
/// ```
/// use godot_core::builtin::{real, reals};
///
Expand Down
2 changes: 1 addition & 1 deletion godot-core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand Down
25 changes: 14 additions & 11 deletions godot-core/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
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(),
};

Expand Down Expand Up @@ -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
}
}

Expand All @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions godot-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::any::Any + Send>) {
if let Some(s) = err.downcast_ref::<&'static str>() {
print_panic_message(s);
Expand Down
4 changes: 4 additions & 0 deletions godot-core/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion godot-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
}

Expand Down
59 changes: 33 additions & 26 deletions godot-macros/src/derive_from_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,26 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
name,
name_string,
} = decl_get_info(&decl);

let mut body = quote! {
let root = variant.try_to::<godot::builtin::Dictionary>()?;
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! {};
Expand Down Expand Up @@ -89,19 +91,21 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
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<Self, godot::builtin::VariantConversionError> {
variant: &::godot::builtin::Variant
) -> Result<Self, ::godot::builtin::VariantConversionError> {
#body
}
}
Expand All @@ -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()? },
)
Expand All @@ -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::<godot::builtin::Dictionary>()?;
let root = root.try_to::<::godot::builtin::Dictionary>()?;
#(
#set_idents
)*
Expand All @@ -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>()?;
}
},
Expand All @@ -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::<godot::builtin::VariantArray>()?;
let mut root = root.try_to::<::godot::builtin::VariantArray>()?;
#(
#ident_set
)*
Expand Down Expand Up @@ -202,7 +206,7 @@ fn make_enum_new_type(
) -> TokenStream {
let field_type = &field.ty;
quote! {
if let Ok(child) = root.try_to::<godot::builtin::Dictionary>() {
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>()?));
}
Expand All @@ -217,7 +221,7 @@ fn make_enum_new_type_skipped(
) -> TokenStream {
let field_type = &field.ty;
quote! {
if let Ok(child) = root.try_to::<godot::builtin::Dictionary>() {
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(
Expand All @@ -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>()?;
}
};
Expand All @@ -253,10 +257,10 @@ fn make_enum_tuple(
let (idents, set_idents): (Vec<_>, Vec<_>) = fields.unzip();

quote! {
let child = root.try_to::<godot::builtin::Dictionary>();
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::<godot::builtin::VariantArray>()?;
let mut variant = variant.try_to::<::godot::builtin::VariantArray>()?;
#(#set_idents)*
return Ok(Self::#variant_name(#(#idents ,)*));
}
Expand All @@ -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::<godot::builtin::Dictionary>() {
if let Ok(root) = root.try_to::<::godot::builtin::Dictionary>() {
if let Some(variant) = root.get(#variant_name_string) {
let variant = variant.try_to::<godot::builtin::Dictionary>()?;
let variant = variant.try_to::<::godot::builtin::Dictionary>()?;
#(
#set_fields
)*
return Ok(Self::#variant_name{ #(#fields,)* });
return Ok(Self::#variant_name {
#( #fields, )*
});
}
}
}
Expand Down
26 changes: 25 additions & 1 deletion godot-macros/src/derive_godot_class/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
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;
Expand All @@ -59,6 +61,7 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {

#godot_init_impl
#godot_exports_impl
#config_impl

::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
class_name: #class_name_obj,
Expand All @@ -77,6 +80,7 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
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")? {
Expand All @@ -88,12 +92,17 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
has_generated_init = true;
}

if parser.handle_alone("tool")? {
is_tool = true;
}

parser.finish()?;
}

Ok(ClassAttributes {
base_ty,
has_generated_init,
is_tool,
})
}

Expand Down Expand Up @@ -144,6 +153,7 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
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)?;
Expand Down Expand Up @@ -171,6 +181,7 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
struct ClassAttributes {
base_ty: Ident,
has_generated_init: bool,
is_tool: bool,
}

struct Fields {
Expand Down Expand Up @@ -210,7 +221,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, }
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 720824a

Please sign in to comment.