Skip to content

Commit 671e0ac

Browse files
committed
Added an #[export()] attribute to GDClass members.
No support currently for any VariantTypes other than ints.
1 parent eaefd45 commit 671e0ac

File tree

7 files changed

+185
-11
lines changed

7 files changed

+185
-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: 116 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,55 @@ impl ExportedField {
208224
}
209225
}
210226

227+
struct ExportedField {
228+
field: Field,
229+
getter: String,
230+
setter: String,
231+
}
232+
233+
impl ExportedField {
234+
pub fn new_from_kv(
235+
field: Field,
236+
attr: &Attribute,
237+
mut map: KvMap,
238+
) -> Result<ExportedField, venial::Error> {
239+
let export_getter: String;
240+
let export_setter: String;
241+
if let Some(getter) = map.remove("getter") {
242+
if let KvValue::Lit(getter) = getter {
243+
export_getter = getter;
244+
} else {
245+
return Err(bail_error(
246+
"#[property] attribute with a non-literal getter",
247+
attr,
248+
));
249+
}
250+
} else {
251+
return Err(bail_error("#[property] attribute without a getter", attr));
252+
}
253+
if let Some(setter) = map.remove("setter") {
254+
if let KvValue::Lit(setter) = setter {
255+
export_setter = setter;
256+
} else {
257+
return Err(bail_error(
258+
"#[property] attribute with a non-literal setter",
259+
attr,
260+
));
261+
}
262+
} else {
263+
return Err(bail_error("#[property] attribute without a setter", attr));
264+
}
265+
ensure_kv_empty(map, attr.__span())?;
266+
return Ok(ExportedField {
267+
field,
268+
getter: export_getter,
269+
setter: export_setter,
270+
});
271+
}
272+
}
273+
211274
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
212-
let base_init = if let Some(ExportedField { name, .. }) = fields.base_field {
275+
let base_init = if let Some(Field { name, .. }) = fields.base_field {
213276
quote! { #name: base, }
214277
} else {
215278
TokenStream::new()
@@ -232,7 +295,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
232295
}
233296

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

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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ func test_to_string() -> bool:
2424

2525
print("to_string: ", s)
2626
print("to_string: ", ffi)
27-
return true
27+
return true
28+
29+
func test_export() -> bool:
30+
var obj = HasProperty.new()
31+
obj.val = 5
32+
print("[GD] HasProperty's property is: ", obj.val, " and should be 5")
33+
return obj.val == 5

itest/rust/src/export_test.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 crate::itest;
8+
use godot::prelude::*;
9+
10+
pub(crate) fn run() -> bool {
11+
let mut ok = true;
12+
// No tests currently, tests using HasProperty are in Godot scripts.
13+
14+
ok
15+
}
16+
17+
#[derive(GodotClass)]
18+
#[class(base=Node)]
19+
struct HasProperty {
20+
#[export(getter = "get_val", setter = "set_val")]
21+
val: i32,
22+
#[base]
23+
base: Base<Node>,
24+
}
25+
26+
#[godot_api]
27+
impl HasProperty {
28+
#[func]
29+
pub fn get_val(&self) -> i32 {
30+
return self.val;
31+
}
32+
33+
#[func]
34+
pub fn set_val(&mut self, val: i32) {
35+
self.val = val;
36+
}
37+
}
38+
39+
#[godot_api]
40+
impl GodotExt for HasProperty {
41+
fn init(base: Base<Node>) -> Self {
42+
HasProperty { val: 0, base }
43+
}
44+
}

itest/rust/src/lib.rs

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

1212
mod base_test;
1313
mod enum_test;
14+
mod export_test;
1415
mod gdscript_ffi_test;
1516
mod node_test;
1617
mod object_test;
@@ -27,6 +28,7 @@ fn run_tests() -> bool {
2728
ok &= node_test::run();
2829
ok &= enum_test::run();
2930
ok &= object_test::run();
31+
ok &= export_test::run();
3032
ok &= singleton_test::run();
3133
ok &= string_test::run();
3234
ok &= utilities_test::run();

0 commit comments

Comments
 (0)