Skip to content

Commit

Permalink
Use the class_name for the field when building PropertyInfo
Browse files Browse the repository at this point in the history
Godot uses the class_name to construct an instance of the right type in a few places,
in particular for the PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT flag.

Partial fix for godot-rust#440
  • Loading branch information
HenryWConklin committed Oct 5, 2023
1 parent 88a7934 commit c240aae
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
8 changes: 5 additions & 3 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
continue;
};

let field_variant_type = util::property_variant_type(field_type);
let field_class_name = util::property_variant_class_name(field_type);
let field_name = field_ident.to_string();

// rustfmt wont format this if we put it in the let-else.
Expand Down Expand Up @@ -178,8 +180,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
let usage = #usage_flags;

let property_info = ::godot::builtin::meta::PropertyInfo {
variant_type: <<#field_type as ::godot::bind::property::Property>::Intermediate as ::godot::builtin::meta::VariantMetadata>::variant_type(),
class_name: #class_name_obj,
variant_type: #field_variant_type,
class_name: #field_class_name,
property_name: #field_name.into(),
hint,
hint_string,
Expand All @@ -194,7 +196,7 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
unsafe {
::godot::sys::interface_fn!(classdb_register_extension_class_property)(
::godot::sys::get_library(),
#class_name::class_name().string_sys(),
#class_name_obj.string_sys(),
std::ptr::addr_of!(property_info_sys),
setter_name.string_sys(),
getter_name.string_sys(),
Expand Down
10 changes: 10 additions & 0 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ pub fn class_name_obj(class: &impl ToTokens) -> TokenStream {
quote! { <#class as ::godot::obj::GodotClass>::class_name() }
}

pub fn property_variant_type(property_type: &impl ToTokens) -> TokenStream {
let property_type = property_type.to_token_stream();
quote! {<<#property_type as ::godot::bind::property::Property>::Intermediate as ::godot::builtin::meta::VariantMetadata>::variant_type()}
}

pub fn property_variant_class_name(property_type: &impl ToTokens) -> TokenStream {
let property_type = property_type.to_token_stream();
quote! {<<#property_type as ::godot::bind::property::Property>::Intermediate as ::godot::builtin::meta::VariantMetadata>::class_name()}
}

pub fn bail_fn<R, T>(msg: impl AsRef<str>, tokens: T) -> ParseResult<R>
where
T: Spanned,
Expand Down
85 changes: 80 additions & 5 deletions itest/rust/src/object_tests/property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

use godot::{
bind::property::ExportInfo,
engine::{global::PropertyHint, Texture},
engine::{
global::{PropertyHint, PropertyUsageFlags},
Texture,
},
prelude::*,
test::itest,
};
Expand Down Expand Up @@ -351,10 +354,8 @@ fn derive_export() {
.iter_shared()
.find(|c| c.get_or_nil("name") == "foo".to_variant())
.unwrap();
assert_eq!(
property.get_or_nil("class_name"),
"DeriveExport".to_variant()
);
// `class_name` should be empty for non-Object variants.
assert_eq!(property.get_or_nil("class_name"), "".to_variant());
assert_eq!(
property.get_or_nil("type"),
(VariantType::Int as i32).to_variant()
Expand All @@ -367,4 +368,78 @@ fn derive_export() {
property.get_or_nil("hint_string"),
"A:0,B:1,C:2".to_variant()
);
assert_eq!(
property.get_or_nil("usage"),
PropertyUsageFlags::PROPERTY_USAGE_DEFAULT
.ord()
.to_variant()
);
}

#[derive(GodotClass)]
#[class(base=Resource)]
pub struct CustomResource {}

#[godot_api]
impl CustomResource {}

#[godot_api]
impl ResourceVirtual for CustomResource {
fn init(_base: godot::obj::Base<Self::Base>) -> Self {
Self {}
}
}

#[derive(GodotClass)]
#[class(base=Node)]
pub struct ExportResource {
#[export]
#[var(usage_flags=[PROPERTY_USAGE_DEFAULT, PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT])]
pub bar: Option<Gd<CustomResource>>,
}

#[godot_api]
impl ExportResource {}

#[godot_api]
impl NodeVirtual for ExportResource {
fn init(_base: godot::obj::Base<Self::Base>) -> Self {
Self { bar: None }
}
}

#[itest]
fn export_resource() {
let class: Gd<ExportResource> = Gd::new_default();

let property = class
.get_property_list()
.iter_shared()
.find(|c| c.get_or_nil("name") == "bar".to_variant())
.unwrap();
// `class_name` should be empty for non-Object variants.
assert_eq!(
property.get_or_nil("class_name"),
"CustomResource".to_variant()
);
assert_eq!(
property.get_or_nil("type"),
(VariantType::Object as i32).to_variant()
);
assert_eq!(
property.get_or_nil("hint"),
(PropertyHint::PROPERTY_HINT_RESOURCE_TYPE.ord()).to_variant()
);
assert_eq!(
property.get_or_nil("hint_string"),
"CustomResource".to_variant()
);
assert_eq!(
property.get_or_nil("usage"),
(PropertyUsageFlags::PROPERTY_USAGE_DEFAULT.ord()
| PropertyUsageFlags::PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT.ord())
.to_variant()
);

class.free();
}

0 comments on commit c240aae

Please sign in to comment.