Skip to content

Commit edf2fe5

Browse files
Merge #46
46: Add variant type support to #[export] attributes r=Bromeon a=mhoff12358 In progress, kv maps should probably support venial::Path types in addition to literals and idents in order to make this work. Co-authored-by: mhoff <[email protected]>
2 parents 6d0507e + 9999333 commit edf2fe5

File tree

7 files changed

+245
-11
lines changed

7 files changed

+245
-11
lines changed

godot-core/src/obj/traits.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ pub mod cap {
100100
fn __register_methods();
101101
}
102102

103+
pub trait ImplementsGodotExports: GodotClass {
104+
#[doc(hidden)]
105+
fn __register_exports();
106+
}
107+
103108
/// Auto-implemented for `#[godot_api] impl GodotExt for MyClass` blocks
104109
pub trait ImplementsGodotExt: GodotClass {
105110
#[doc(hidden)]

godot-core/src/registry.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,13 +374,16 @@ pub mod callbacks {
374374
T::register_class(&mut class_builder);
375375
}
376376

377-
pub fn register_user_binds<T: cap::ImplementsGodotApi>(_class_builder: &mut dyn Any) {
377+
pub fn register_user_binds<T: cap::ImplementsGodotApi + cap::ImplementsGodotExports>(
378+
_class_builder: &mut dyn Any,
379+
) {
378380
// let class_builder = class_builder
379381
// .downcast_mut::<ClassBuilder<T>>()
380382
// .expect("bad type erasure");
381383

382384
//T::register_methods(class_builder);
383385
T::__register_methods();
386+
T::__register_exports();
384387
}
385388
}
386389

godot-macros/src/derive_godot_class.rs

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate::util::{bail, ensure_kv_empty, ident, path_is_single, KvMap, KvValue};
7+
use crate::util::{
8+
bail, bail_error, ensure_kv_empty, ident, parse_kv_group, path_is_single, KvMap, KvValue,
9+
};
810
use crate::{util, ParseResult};
911
use proc_macro2::{Ident, Punct, Span, TokenStream};
1012
use quote::spanned::Spanned;
@@ -30,6 +32,8 @@ pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
3032
let prv = quote! { ::godot::private };
3133
let deref_impl = make_deref_impl(class_name, &fields);
3234

35+
let godot_exports_impl = make_exports_impl(class_name, &fields);
36+
3337
let (godot_init_impl, create_fn);
3438
if struct_cfg.has_generated_init {
3539
godot_init_impl = make_godot_init_impl(class_name, fields);
@@ -49,6 +53,7 @@ pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
4953
}
5054

5155
#godot_init_impl
56+
#godot_exports_impl
5257
#deref_impl
5358

5459
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
@@ -111,7 +116,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
111116
fn parse_fields(class: &Struct) -> ParseResult<Fields> {
112117
let mut all_field_names = vec![];
113118
let mut exported_fields = vec![];
114-
let mut base_field = Option::<ExportedField>::None;
119+
let mut base_field = Option::<Field>::None;
115120

116121
let fields: Vec<(NamedField, Punct)> = match &class.fields {
117122
StructFields::Unit => {
@@ -142,9 +147,18 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
142147
attr,
143148
)?;
144149
}
145-
base_field = Some(ExportedField::new(&field))
150+
base_field = Some(Field::new(&field))
146151
} else if path == "export" {
147-
exported_fields.push(ExportedField::new(&field))
152+
match parse_kv_group(&attr.value) {
153+
Ok(export_kv) => {
154+
let exported_field =
155+
ExportedField::new_from_kv(Field::new(&field), &attr, export_kv)?;
156+
exported_fields.push(exported_field);
157+
}
158+
Err(error) => {
159+
return Err(error);
160+
}
161+
}
148162
}
149163
}
150164
}
@@ -158,6 +172,7 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
158172
Ok(Fields {
159173
all_field_names,
160174
base_field,
175+
exported_fields,
161176
})
162177
}
163178

@@ -191,15 +206,16 @@ struct ClassAttributes {
191206

192207
struct Fields {
193208
all_field_names: Vec<Ident>,
194-
base_field: Option<ExportedField>,
209+
base_field: Option<Field>,
210+
exported_fields: Vec<ExportedField>,
195211
}
196212

197-
struct ExportedField {
213+
struct Field {
198214
name: Ident,
199215
_ty: TyExpr,
200216
}
201217

202-
impl ExportedField {
218+
impl Field {
203219
fn new(field: &NamedField) -> Self {
204220
Self {
205221
name: field.name.clone(),
@@ -208,8 +224,54 @@ impl ExportedField {
208224
}
209225
}
210226

227+
struct ExportedField {
228+
field: Field,
229+
getter: String,
230+
setter: String,
231+
variant_type: String,
232+
}
233+
234+
impl ExportedField {
235+
pub fn new_from_kv(
236+
field: Field,
237+
attr: &Attribute,
238+
mut map: KvMap,
239+
) -> ParseResult<ExportedField> {
240+
let getter = Self::require_key_value(&mut map, "getter", attr)?;
241+
let setter = Self::require_key_value(&mut map, "setter", attr)?;
242+
let variant_type = Self::require_key_value(&mut map, "variant_type", attr)?;
243+
244+
ensure_kv_empty(map, attr.__span())?;
245+
246+
return Ok(ExportedField {
247+
field,
248+
getter,
249+
setter,
250+
variant_type,
251+
});
252+
}
253+
254+
fn require_key_value(map: &mut KvMap, key: &str, attr: &Attribute) -> ParseResult<String> {
255+
if let Some(value) = map.remove(key) {
256+
if let KvValue::Lit(value) = value {
257+
return Ok(value);
258+
} else {
259+
return bail(
260+
format!(
261+
"#[export] attribute {} with a non-literal variant_type",
262+
key
263+
),
264+
attr,
265+
)?;
266+
}
267+
} else {
268+
return bail(format!("#[export] attribute without a {}", key), attr);
269+
}
270+
}
271+
}
272+
211273
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
212-
let base_init = if let Some(ExportedField { name, .. }) = fields.base_field {
274+
let base_init = if let Some(Field { name, .. }) = fields.base_field {
213275
quote! { #name: base, }
214276
} else {
215277
TokenStream::new()
@@ -232,7 +294,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
232294
}
233295

234296
fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
235-
let base_field = if let Some(ExportedField { name, .. }) = &fields.base_field {
297+
let base_field = if let Some(Field { name, .. }) = &fields.base_field {
236298
name
237299
} else {
238300
return TokenStream::new();
@@ -253,3 +315,49 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
253315
}
254316
}
255317
}
318+
319+
fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
320+
let export_tokens = fields
321+
.exported_fields
322+
.iter()
323+
.map(|exported_field: &ExportedField| {
324+
use std::str::FromStr;
325+
let name = exported_field.field.name.to_string();
326+
let getter = proc_macro2::Literal::from_str(&exported_field.getter).unwrap();
327+
let setter = proc_macro2::Literal::from_str(&exported_field.setter).unwrap();
328+
let vtype = &exported_field.variant_type;
329+
let variant_type: TokenStream = vtype[1..vtype.len() - 1].parse().unwrap();
330+
quote! {
331+
let class_name = ::godot::builtin::StringName::from(#class_name::CLASS_NAME);
332+
let property_info = ::godot::builtin::meta::PropertyInfo::new(
333+
#variant_type,
334+
::godot::builtin::meta::ClassName::new::<#class_name>(),
335+
::godot::builtin::StringName::from(#name),
336+
);
337+
let property_info_sys = property_info.property_sys();
338+
339+
let getter_string_name = ::godot::builtin::StringName::from(#getter);
340+
let setter_string_name = ::godot::builtin::StringName::from(#setter);
341+
unsafe {
342+
::godot::sys::interface_fn!(classdb_register_extension_class_property)(
343+
::godot::sys::get_library(),
344+
class_name.string_sys(),
345+
std::ptr::addr_of!(property_info_sys),
346+
setter_string_name.string_sys(),
347+
getter_string_name.string_sys(),
348+
);
349+
}
350+
}
351+
});
352+
quote! {
353+
impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
354+
fn __register_exports() {
355+
#(
356+
{
357+
#export_tokens
358+
}
359+
)*
360+
}
361+
}
362+
}
363+
}

godot-macros/src/util.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ pub fn strlit(s: &str) -> Literal {
2222
Literal::string(s)
2323
}
2424

25+
pub fn bail_error<T>(msg: impl AsRef<str>, tokens: T) -> Error
26+
where
27+
T: Spanned,
28+
{
29+
Error::new_at_span(tokens.__span(), msg.as_ref())
30+
}
31+
2532
pub fn bail<R, T>(msg: impl AsRef<str>, tokens: T) -> Result<R, Error>
2633
where
2734
T: Spanned,

itest/godot/ManualFfiTests.gd

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func run() -> bool:
99
var ok = true
1010
#ok = ok && test_missing_init()
1111
ok = ok && test_to_string()
12+
ok = ok && test_export()
1213

1314
print("[GD] ManualFfi tested (passed=", ok, ")")
1415
return ok
@@ -24,4 +25,24 @@ func test_to_string() -> bool:
2425

2526
print("to_string: ", s)
2627
print("to_string: ", ffi)
27-
return true
28+
return true
29+
30+
func test_export() -> bool:
31+
var obj = HasProperty.new()
32+
33+
obj.int_val = 5
34+
print("[GD] HasProperty's int_val property is: ", obj.int_val, " and should be 5")
35+
var int_val_correct = obj.int_val == 5
36+
37+
obj.string_val = "test val"
38+
print("[GD] HasProperty's string_val property is: ", obj.string_val, " and should be \"test val\"")
39+
var string_val_correct = obj.string_val == "test val"
40+
41+
var node = Node.new()
42+
obj.object_val = node
43+
print("[GD] HasProperty's object_val property is: ", obj.object_val, " and should be ", node)
44+
var object_val_correct = obj.object_val == node
45+
46+
obj.free()
47+
48+
return int_val_correct && string_val_correct && object_val_correct

itest/rust/src/export_test.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use godot::prelude::*;
8+
9+
pub(crate) fn run() -> bool {
10+
let ok = true;
11+
// No tests currently, tests using HasProperty are in Godot scripts.
12+
13+
ok
14+
}
15+
16+
#[derive(GodotClass)]
17+
#[class(base=Node)]
18+
struct HasProperty {
19+
#[base]
20+
base: Base<Node>,
21+
#[export(
22+
getter = "get_int_val",
23+
setter = "set_int_val",
24+
variant_type = "::godot::sys::VariantType::Int"
25+
)]
26+
int_val: i32,
27+
#[export(
28+
getter = "get_string_val",
29+
setter = "set_string_val",
30+
variant_type = "::godot::sys::VariantType::String"
31+
)]
32+
string_val: GodotString,
33+
#[export(
34+
getter = "get_object_val",
35+
setter = "set_object_val",
36+
variant_type = "::godot::sys::VariantType::Object"
37+
)]
38+
object_val: Option<Gd<Object>>,
39+
}
40+
41+
#[godot_api]
42+
impl HasProperty {
43+
#[func]
44+
pub fn get_int_val(&self) -> i32 {
45+
return self.int_val;
46+
}
47+
48+
#[func]
49+
pub fn set_int_val(&mut self, val: i32) {
50+
self.int_val = val;
51+
}
52+
53+
#[func]
54+
pub fn get_string_val(&self) -> GodotString {
55+
return self.string_val.clone();
56+
}
57+
58+
#[func]
59+
pub fn set_string_val(&mut self, val: GodotString) {
60+
self.string_val = val;
61+
}
62+
63+
#[func]
64+
pub fn get_object_val(&self) -> Variant {
65+
if let Some(object_val) = self.object_val.as_ref() {
66+
return object_val.to_variant();
67+
} else {
68+
return Variant::nil();
69+
}
70+
}
71+
72+
#[func]
73+
pub fn set_object_val(&mut self, val: Gd<Object>) {
74+
self.object_val = Some(val);
75+
}
76+
}
77+
78+
#[godot_api]
79+
impl GodotExt for HasProperty {
80+
fn init(base: Base<Node>) -> Self {
81+
HasProperty {
82+
int_val: 0,
83+
object_val: None,
84+
string_val: GodotString::new(),
85+
base,
86+
}
87+
}
88+
}

itest/rust/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::panic::UnwindSafe;
1414

1515
mod base_test;
1616
mod enum_test;
17+
mod export_test;
1718
mod gdscript_ffi_test;
1819
mod node_test;
1920
mod object_test;
@@ -30,6 +31,7 @@ fn run_tests() -> bool {
3031
ok &= node_test::run();
3132
ok &= enum_test::run();
3233
ok &= object_test::run();
34+
ok &= export_test::run();
3335
ok &= singleton_test::run();
3436
ok &= string_test::run();
3537
ok &= utilities_test::run();

0 commit comments

Comments
 (0)