Skip to content

Add groups export. #1214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion godot-core/src/registry/godot_register_wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

//! Internal registration machinery used by proc-macro APIs.

use crate::builtin::StringName;
use crate::builtin::{GString, StringName};
use crate::global::PropertyUsageFlags;
use crate::meta::{ClassName, GodotConvert, GodotType, PropertyHintInfo, PropertyInfo};
use crate::obj::GodotClass;
Expand Down Expand Up @@ -79,3 +79,33 @@ fn register_var_or_export_inner(
);
}
}

pub fn register_group<C: GodotClass>(group_name: &str, prefix: &str) {
let group_name = GString::from(group_name);
let prefix = GString::from(prefix);
let class_name = C::class_name();

unsafe {
sys::interface_fn!(classdb_register_extension_class_property_group)(
sys::get_library(),
class_name.string_sys(),
group_name.string_sys(),
prefix.string_sys(),
);
}
}

pub fn register_subgroup<C: GodotClass>(subgroup_name: &str, prefix: &str) {
let subgroup_name = GString::from(subgroup_name);
let prefix = GString::from(prefix);
let class_name = C::class_name();

unsafe {
sys::interface_fn!(classdb_register_extension_class_property_subgroup)(
sys::get_library(),
class_name.string_sys(),
subgroup_name.string_sys(),
prefix.string_sys(),
);
}
}
19 changes: 5 additions & 14 deletions godot-macros/src/class/data_models/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::class::data_models::group_export::FieldGroup;
use crate::class::{FieldExport, FieldVar};
use crate::util::{error, KvParser};
use proc_macro2::{Ident, Span, TokenStream};
Expand All @@ -16,6 +17,8 @@ pub struct Field {
pub default_val: Option<FieldDefault>,
pub var: Option<FieldVar>,
pub export: Option<FieldExport>,
pub group: Option<FieldGroup>,
pub subgroup: Option<FieldGroup>,
pub is_onready: bool,
pub is_oneditor: bool,
#[cfg(feature = "register-docs")]
Expand All @@ -31,6 +34,8 @@ impl Field {
default_val: None,
var: None,
export: None,
group: None,
subgroup: None,
is_onready: false,
is_oneditor: false,
#[cfg(feature = "register-docs")]
Expand Down Expand Up @@ -110,20 +115,6 @@ pub enum FieldCond {
IsOnEditor,
}

pub struct Fields {
/// All fields except `base_field`.
pub all_fields: Vec<Field>,

/// The field with type `Base<T>`, if available.
pub base_field: Option<Field>,

/// Deprecation warnings.
pub deprecations: Vec<TokenStream>,

/// Errors during macro evaluation that shouldn't abort the execution of the macro.
pub errors: Vec<venial::Error>,
}

#[derive(Clone)]
pub struct FieldDefault {
pub default_val: TokenStream,
Expand Down
47 changes: 47 additions & 0 deletions godot-macros/src/class/data_models/fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::class::Field;
use crate::util::bail;
use crate::ParseResult;
use proc_macro2::{Punct, TokenStream};

pub struct Fields {
/// All fields except `base_field`.
pub all_fields: Vec<Field>,

/// The field with type `Base<T>`, if available.
pub base_field: Option<Field>,

/// Deprecation warnings.
pub deprecations: Vec<TokenStream>,

/// Errors during macro evaluation that shouldn't abort the execution of the macro.
pub errors: Vec<venial::Error>,
}

/// Fetches data for all named fields for a struct.
///
/// Errors if `class` is a tuple struct.
pub fn named_fields(
class: &venial::Struct,
derive_macro_name: &str,
) -> ParseResult<Vec<(venial::NamedField, Punct)>> {
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API
// user than those from parse_struct_attributes, so this must be run first.
match &class.fields {
// TODO disallow unit structs in the future
// It often happens that over time, a registered class starts to require a base field.
// Extending a {} struct requires breaking less code, so we should encourage it from the start.
venial::Fields::Unit => Ok(vec![]),
venial::Fields::Tuple(_) => bail!(
&class.fields,
"{derive_macro_name} is not supported for tuple structs",
)?,
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()),
}
}
38 changes: 38 additions & 0 deletions godot-macros/src/class/data_models/group_export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

// Note: group membership for properties in Godot is based on the order of their registration.
// All the properties belong to group or subgroup registered beforehand, identically as in GDScript.
// Initial implementation providing clap-like API with an explicit sorting
// & groups/subgroups declared for each field (`#[export(group = ..., subgroup = ...)]`
// can be found at: https://github.com/godot-rust/gdext/pull/1214.

use crate::util::{bail, KvParser};
use crate::ParseResult;
use proc_macro2::Literal;

/// Specifies group or subgroup which starts with a given field.
/// Group membership for properties in Godot is based on the order of their registration –
/// i.e. given field belongs to group declared beforehand (for example with some previous field).
pub struct FieldGroup {
pub(crate) name: Literal,
pub(crate) prefix: Literal,
}

impl FieldGroup {
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> {
let Some(name) = parser.handle_literal("name", "String")? else {
return bail!(parser.span(), "missing required argument: `name = \"...\".");
};

let prefix = parser
.handle_literal("prefix", "String")?
.unwrap_or(Literal::string(""));

Ok(Self { name, prefix })
}
}
57 changes: 49 additions & 8 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

//! Parses the `#[var]` and `#[export]` attributes on fields.

use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct};
use crate::class::data_models::fields::Fields;
use crate::class::data_models::group_export::FieldGroup;
use crate::class::{Field, FieldVar, GetSet, GetterSetterImpl, UsageFlags};
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct, ident};
use proc_macro2::{Ident, TokenStream};
use quote::quote;

Expand Down Expand Up @@ -48,6 +50,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
ty: field_type,
var,
export,
group,
subgroup,
..
} = field;

Expand All @@ -59,18 +63,17 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
} else {
UsageFlags::InferredExport
};
Some(FieldVar {
FieldVar {
usage_flags,
..Default::default()
})
}
}

(_, var) => var.clone(),
(_, Some(var)) => var.clone(),
_ => continue,
};

let Some(var) = var else {
continue;
};
make_groups_registrations(group, subgroup, &mut export_tokens, class_name);

let field_name = field_ident.to_string();

Expand Down Expand Up @@ -220,3 +223,41 @@ fn make_getter_setter(

quote! { #funcs_collection::#constant }
}

/// Generates registrations for declared group and subgroup and pushes them to export tokens.
///
/// Groups must be registered before subgroups (otherwise the ordering is broken).
fn make_groups_registrations(
group: &Option<FieldGroup>,
subgroup: &Option<FieldGroup>,
export_tokens: &mut Vec<TokenStream>,
class_name: &Ident,
) {
export_tokens.push(make_group_registration(
group,
ident("register_group"),
class_name,
));
export_tokens.push(make_group_registration(
subgroup,
ident("register_subgroup"),
class_name,
));
}

fn make_group_registration(
group: &Option<FieldGroup>,
register_fn: Ident,
class_name: &Ident,
) -> TokenStream {
let Some(FieldGroup { name, prefix }) = group else {
return TokenStream::new();
};

quote! {
::godot::register::private::#register_fn::<#class_name>(
#name,
#prefix
);
}
}
44 changes: 21 additions & 23 deletions godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::class::data_models::fields::{named_fields, Fields};
use crate::class::data_models::group_export::FieldGroup;
use crate::class::{
make_property_impl, make_virtual_callback, BeforeKind, Field, FieldCond, FieldDefault,
FieldExport, FieldVar, Fields, SignatureInfo,
FieldExport, FieldVar, SignatureInfo,
};
use crate::util::{
bail, error, format_funcs_collection_struct, ident, path_ends_with_complex,
Expand All @@ -33,9 +35,10 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
}

let mut modifiers = Vec::new();
let named_fields = named_fields(class)?;
let named_fields = named_fields(class, "#[derive(GodotClass)]")?;
let mut struct_cfg = parse_struct_attributes(class)?;
let mut fields = parse_fields(named_fields, struct_cfg.init_strategy)?;

if struct_cfg.is_editor_plugin() {
modifiers.push(quote! { with_editor_plugin })
}
Expand Down Expand Up @@ -559,25 +562,6 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
})
}

/// Fetches data for all named fields for a struct.
///
/// Errors if `class` is a tuple struct.
fn named_fields(class: &venial::Struct) -> ParseResult<Vec<(venial::NamedField, Punct)>> {
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API
// user than those from parse_struct_attributes, so this must be run first.
match &class.fields {
// TODO disallow unit structs in the future
// It often happens that over time, a registered class starts to require a base field.
// Extending a {} struct requires breaking less code, so we should encourage it from the start.
venial::Fields::Unit => Ok(vec![]),
venial::Fields::Tuple(_) => bail!(
&class.fields,
"#[derive(GodotClass)] is not supported for tuple structs",
)?,
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()),
}
}

/// Returns field names and 1 base field, if available.
fn parse_fields(
named_fields: Vec<(venial::NamedField, Punct)>,
Expand Down Expand Up @@ -627,10 +611,10 @@ fn parse_fields(
}

// Deprecated #[init(default = expr)]
if let Some(default) = parser.handle_expr("default")? {
if let Some((key, default)) = parser.handle_expr_with_key("default")? {
if field.default_val.is_some() {
return bail!(
parser.span(),
key,
"Cannot use both `val` and `default` keys in #[init]; prefer using `val`"
);
Comment on lines -630 to 619
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is unrelated to the PR, right? Is it a span improvement?

Note that #[init(default)] will be removed in v0.4; others like #[init(val)] would benefit more from this. But if you plan bigger changes, maybe a separate PR is worth it 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's a span improvement – mainly it makes handle_literal (which is also used by other parsers) return correct position of said literal. default using correct span as well is a side effect.

}
Expand Down Expand Up @@ -683,6 +667,20 @@ fn parse_fields(
parser.finish()?;
}

// #[export_group(name = ..., prefix = ...)]
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_group")? {
let group = FieldGroup::new_from_kv(&mut parser)?;
field.group = Some(group);
parser.finish()?;
}

// #[export_subgroup(name = ..., prefix = ...)]
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_subgroup")? {
let subgroup = FieldGroup::new_from_kv(&mut parser)?;
field.subgroup = Some(subgroup);
parser.finish()?;
}

// #[var]
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "var")? {
let var = FieldVar::new_from_kv(&mut parser)?;
Expand Down
3 changes: 3 additions & 0 deletions godot-macros/src/class/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
mod derive_godot_class;
mod godot_api;
mod godot_dyn;

mod data_models {
pub mod constant;
pub mod field;
pub mod field_export;
pub mod field_var;
pub mod fields;
pub mod func;
pub mod group_export;
pub mod inherent_impl;
pub mod interface_trait_impl;
pub mod property;
Expand Down
Loading
Loading