Skip to content

Commit c6be825

Browse files
authored
Merge pull request #1214 from Yarwin/add-group-export
Add groups export.
2 parents 7be2bd3 + 05cbcff commit c6be825

File tree

10 files changed

+375
-56
lines changed

10 files changed

+375
-56
lines changed

godot-core/src/registry/godot_register_wrappers.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
//! Internal registration machinery used by proc-macro APIs.
99
10-
use crate::builtin::StringName;
10+
use crate::builtin::{GString, StringName};
1111
use crate::global::PropertyUsageFlags;
1212
use crate::meta::{ClassName, GodotConvert, GodotType, PropertyHintInfo, PropertyInfo};
1313
use crate::obj::GodotClass;
@@ -79,3 +79,33 @@ fn register_var_or_export_inner(
7979
);
8080
}
8181
}
82+
83+
pub fn register_group<C: GodotClass>(group_name: &str, prefix: &str) {
84+
let group_name = GString::from(group_name);
85+
let prefix = GString::from(prefix);
86+
let class_name = C::class_name();
87+
88+
unsafe {
89+
sys::interface_fn!(classdb_register_extension_class_property_group)(
90+
sys::get_library(),
91+
class_name.string_sys(),
92+
group_name.string_sys(),
93+
prefix.string_sys(),
94+
);
95+
}
96+
}
97+
98+
pub fn register_subgroup<C: GodotClass>(subgroup_name: &str, prefix: &str) {
99+
let subgroup_name = GString::from(subgroup_name);
100+
let prefix = GString::from(prefix);
101+
let class_name = C::class_name();
102+
103+
unsafe {
104+
sys::interface_fn!(classdb_register_extension_class_property_subgroup)(
105+
sys::get_library(),
106+
class_name.string_sys(),
107+
subgroup_name.string_sys(),
108+
prefix.string_sys(),
109+
);
110+
}
111+
}

godot-macros/src/class/data_models/field.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8+
use crate::class::data_models::group_export::FieldGroup;
89
use crate::class::{FieldExport, FieldVar};
910
use crate::util::{error, KvParser};
1011
use proc_macro2::{Ident, Span, TokenStream};
@@ -16,6 +17,8 @@ pub struct Field {
1617
pub default_val: Option<FieldDefault>,
1718
pub var: Option<FieldVar>,
1819
pub export: Option<FieldExport>,
20+
pub group: Option<FieldGroup>,
21+
pub subgroup: Option<FieldGroup>,
1922
pub is_onready: bool,
2023
pub is_oneditor: bool,
2124
#[cfg(feature = "register-docs")]
@@ -31,6 +34,8 @@ impl Field {
3134
default_val: None,
3235
var: None,
3336
export: None,
37+
group: None,
38+
subgroup: None,
3439
is_onready: false,
3540
is_oneditor: false,
3641
#[cfg(feature = "register-docs")]
@@ -110,20 +115,6 @@ pub enum FieldCond {
110115
IsOnEditor,
111116
}
112117

113-
pub struct Fields {
114-
/// All fields except `base_field`.
115-
pub all_fields: Vec<Field>,
116-
117-
/// The field with type `Base<T>`, if available.
118-
pub base_field: Option<Field>,
119-
120-
/// Deprecation warnings.
121-
pub deprecations: Vec<TokenStream>,
122-
123-
/// Errors during macro evaluation that shouldn't abort the execution of the macro.
124-
pub errors: Vec<venial::Error>,
125-
}
126-
127118
#[derive(Clone)]
128119
pub struct FieldDefault {
129120
pub default_val: TokenStream,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use crate::class::Field;
9+
use crate::util::bail;
10+
use crate::ParseResult;
11+
use proc_macro2::{Punct, TokenStream};
12+
13+
pub struct Fields {
14+
/// All fields except `base_field`.
15+
pub all_fields: Vec<Field>,
16+
17+
/// The field with type `Base<T>`, if available.
18+
pub base_field: Option<Field>,
19+
20+
/// Deprecation warnings.
21+
pub deprecations: Vec<TokenStream>,
22+
23+
/// Errors during macro evaluation that shouldn't abort the execution of the macro.
24+
pub errors: Vec<venial::Error>,
25+
}
26+
27+
/// Fetches data for all named fields for a struct.
28+
///
29+
/// Errors if `class` is a tuple struct.
30+
pub fn named_fields(
31+
class: &venial::Struct,
32+
derive_macro_name: &str,
33+
) -> ParseResult<Vec<(venial::NamedField, Punct)>> {
34+
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API
35+
// user than those from parse_struct_attributes, so this must be run first.
36+
match &class.fields {
37+
// TODO disallow unit structs in the future
38+
// It often happens that over time, a registered class starts to require a base field.
39+
// Extending a {} struct requires breaking less code, so we should encourage it from the start.
40+
venial::Fields::Unit => Ok(vec![]),
41+
venial::Fields::Tuple(_) => bail!(
42+
&class.fields,
43+
"{derive_macro_name} is not supported for tuple structs",
44+
)?,
45+
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()),
46+
}
47+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
// Note: group membership for properties in Godot is based on the order of their registration.
9+
// All the properties belong to group or subgroup registered beforehand, identically as in GDScript.
10+
// Initial implementation providing clap-like API with an explicit sorting
11+
// & groups/subgroups declared for each field (`#[export(group = ..., subgroup = ...)]`
12+
// can be found at: https://github.com/godot-rust/gdext/pull/1214.
13+
14+
use crate::util::{bail, KvParser};
15+
use crate::ParseResult;
16+
use proc_macro2::Literal;
17+
18+
/// Specifies group or subgroup which starts with a given field.
19+
/// Group membership for properties in Godot is based on the order of their registration –
20+
/// i.e. given field belongs to group declared beforehand (for example with some previous field).
21+
pub struct FieldGroup {
22+
pub(crate) name: Literal,
23+
pub(crate) prefix: Literal,
24+
}
25+
26+
impl FieldGroup {
27+
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> {
28+
let Some(name) = parser.handle_literal("name", "String")? else {
29+
return bail!(parser.span(), "missing required argument: `name = \"...\".");
30+
};
31+
32+
let prefix = parser
33+
.handle_literal("prefix", "String")?
34+
.unwrap_or(Literal::string(""));
35+
36+
Ok(Self { name, prefix })
37+
}
38+
}

godot-macros/src/class/data_models/property.rs

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
//! Parses the `#[var]` and `#[export]` attributes on fields.
99
10-
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
11-
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct};
10+
use crate::class::data_models::fields::Fields;
11+
use crate::class::data_models::group_export::FieldGroup;
12+
use crate::class::{Field, FieldVar, GetSet, GetterSetterImpl, UsageFlags};
13+
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct, ident};
1214
use proc_macro2::{Ident, TokenStream};
1315
use quote::quote;
1416

@@ -48,6 +50,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
4850
ty: field_type,
4951
var,
5052
export,
53+
group,
54+
subgroup,
5155
..
5256
} = field;
5357

@@ -59,18 +63,17 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
5963
} else {
6064
UsageFlags::InferredExport
6165
};
62-
Some(FieldVar {
66+
FieldVar {
6367
usage_flags,
6468
..Default::default()
65-
})
69+
}
6670
}
6771

68-
(_, var) => var.clone(),
72+
(_, Some(var)) => var.clone(),
73+
_ => continue,
6974
};
7075

71-
let Some(var) = var else {
72-
continue;
73-
};
76+
make_groups_registrations(group, subgroup, &mut export_tokens, class_name);
7477

7578
let field_name = field_ident.to_string();
7679

@@ -220,3 +223,41 @@ fn make_getter_setter(
220223

221224
quote! { #funcs_collection::#constant }
222225
}
226+
227+
/// Generates registrations for declared group and subgroup and pushes them to export tokens.
228+
///
229+
/// Groups must be registered before subgroups (otherwise the ordering is broken).
230+
fn make_groups_registrations(
231+
group: &Option<FieldGroup>,
232+
subgroup: &Option<FieldGroup>,
233+
export_tokens: &mut Vec<TokenStream>,
234+
class_name: &Ident,
235+
) {
236+
export_tokens.push(make_group_registration(
237+
group,
238+
ident("register_group"),
239+
class_name,
240+
));
241+
export_tokens.push(make_group_registration(
242+
subgroup,
243+
ident("register_subgroup"),
244+
class_name,
245+
));
246+
}
247+
248+
fn make_group_registration(
249+
group: &Option<FieldGroup>,
250+
register_fn: Ident,
251+
class_name: &Ident,
252+
) -> TokenStream {
253+
let Some(FieldGroup { name, prefix }) = group else {
254+
return TokenStream::new();
255+
};
256+
257+
quote! {
258+
::godot::register::private::#register_fn::<#class_name>(
259+
#name,
260+
#prefix
261+
);
262+
}
263+
}

godot-macros/src/class/derive_godot_class.rs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

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

3537
let mut modifiers = Vec::new();
36-
let named_fields = named_fields(class)?;
38+
let named_fields = named_fields(class, "#[derive(GodotClass)]")?;
3739
let mut struct_cfg = parse_struct_attributes(class)?;
3840
let mut fields = parse_fields(named_fields, struct_cfg.init_strategy)?;
41+
3942
if struct_cfg.is_editor_plugin() {
4043
modifiers.push(quote! { with_editor_plugin })
4144
}
@@ -559,25 +562,6 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
559562
})
560563
}
561564

562-
/// Fetches data for all named fields for a struct.
563-
///
564-
/// Errors if `class` is a tuple struct.
565-
fn named_fields(class: &venial::Struct) -> ParseResult<Vec<(venial::NamedField, Punct)>> {
566-
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API
567-
// user than those from parse_struct_attributes, so this must be run first.
568-
match &class.fields {
569-
// TODO disallow unit structs in the future
570-
// It often happens that over time, a registered class starts to require a base field.
571-
// Extending a {} struct requires breaking less code, so we should encourage it from the start.
572-
venial::Fields::Unit => Ok(vec![]),
573-
venial::Fields::Tuple(_) => bail!(
574-
&class.fields,
575-
"#[derive(GodotClass)] is not supported for tuple structs",
576-
)?,
577-
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()),
578-
}
579-
}
580-
581565
/// Returns field names and 1 base field, if available.
582566
fn parse_fields(
583567
named_fields: Vec<(venial::NamedField, Punct)>,
@@ -627,10 +611,10 @@ fn parse_fields(
627611
}
628612

629613
// Deprecated #[init(default = expr)]
630-
if let Some(default) = parser.handle_expr("default")? {
614+
if let Some((key, default)) = parser.handle_expr_with_key("default")? {
631615
if field.default_val.is_some() {
632616
return bail!(
633-
parser.span(),
617+
key,
634618
"Cannot use both `val` and `default` keys in #[init]; prefer using `val`"
635619
);
636620
}
@@ -683,6 +667,20 @@ fn parse_fields(
683667
parser.finish()?;
684668
}
685669

670+
// #[export_group(name = ..., prefix = ...)]
671+
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_group")? {
672+
let group = FieldGroup::new_from_kv(&mut parser)?;
673+
field.group = Some(group);
674+
parser.finish()?;
675+
}
676+
677+
// #[export_subgroup(name = ..., prefix = ...)]
678+
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_subgroup")? {
679+
let subgroup = FieldGroup::new_from_kv(&mut parser)?;
680+
field.subgroup = Some(subgroup);
681+
parser.finish()?;
682+
}
683+
686684
// #[var]
687685
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "var")? {
688686
let var = FieldVar::new_from_kv(&mut parser)?;

godot-macros/src/class/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
mod derive_godot_class;
99
mod godot_api;
1010
mod godot_dyn;
11+
1112
mod data_models {
1213
pub mod constant;
1314
pub mod field;
1415
pub mod field_export;
1516
pub mod field_var;
17+
pub mod fields;
1618
pub mod func;
19+
pub mod group_export;
1720
pub mod inherent_impl;
1821
pub mod interface_trait_impl;
1922
pub mod property;

0 commit comments

Comments
 (0)