From ac4d9ec2983e4cf34f35a566bdf2b9854cd0afb0 Mon Sep 17 00:00:00 2001 From: Yarvin Date: Fri, 31 Jan 2025 23:27:14 +0100 Subject: [PATCH] Merge pull request #991 from godot-rust/feature/compat-virtual-methods Godot FFI: postinit create, compat virtual methods, icon paths --- godot-codegen/src/generator/virtual_traits.rs | 5 ++ godot-core/src/meta/property_info.rs | 38 +++++++++++++ godot-core/src/obj/traits.rs | 8 +++ godot-core/src/registry/callbacks.rs | 23 ++++++++ godot-core/src/registry/class.rs | 3 ++ godot-core/src/registry/plugin.rs | 17 +++++- .../class/data_models/interface_trait_impl.rs | 20 +++++++ itest/rust/src/object_tests/mod.rs | 1 + .../object_tests/validate_property_test.rs | 54 +++++++++++++++++++ 9 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 itest/rust/src/object_tests/validate_property_test.rs diff --git a/godot-codegen/src/generator/virtual_traits.rs b/godot-codegen/src/generator/virtual_traits.rs index d94f8c835..84483ba95 100644 --- a/godot-codegen/src/generator/virtual_traits.rs +++ b/godot-codegen/src/generator/virtual_traits.rs @@ -119,6 +119,11 @@ fn make_special_virtual_methods(notification_enum_name: &Ident) -> TokenStream { unimplemented!() } + #[cfg(since_api = "4.2")] + fn validate_property(&self, property: &mut crate::meta::PropertyInfo) { + unimplemented!() + } + /// Called by Godot to tell if a property has a custom revert or not. /// /// Return `None` for no custom revert, and return `Some(value)` to specify the custom revert. diff --git a/godot-core/src/meta/property_info.rs b/godot-core/src/meta/property_info.rs index f14e63b7d..87402dee8 100644 --- a/godot-core/src/meta/property_info.rs +++ b/godot-core/src/meta/property_info.rs @@ -10,6 +10,7 @@ use crate::global::{PropertyHint, PropertyUsageFlags}; use crate::meta::{ element_godot_type_name, ArrayElement, ClassName, GodotType, PackedArrayElement, }; +use crate::obj::{EngineBitfield, EngineEnum}; use crate::registry::property::{Export, Var}; use crate::sys; use godot_ffi::VariantType; @@ -179,6 +180,43 @@ impl PropertyInfo { } } + /// Consumes self, updating the `sys::GDExtensionPropertyInfo` pointer. + /// + /// # Safety + /// + /// * `property_info_ptr` must be valid. + pub(crate) unsafe fn update(self, property_info_ptr: *mut sys::GDExtensionPropertyInfo) { + (*property_info_ptr).type_ = self.variant_type.sys(); + (*property_info_ptr).name = self.property_name.into_owned_string_sys(); + (*property_info_ptr).hint = u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()"); + (*property_info_ptr).hint_string = self.hint_info.hint_string.into_owned_string_sys(); + (*property_info_ptr).usage = u32::try_from(self.usage.ord()).expect("usage.ord()"); + (*property_info_ptr).class_name = sys::SysPtr::force_mut(self.class_name.string_sys()); + } + + /// Creates copy of given `sys::GDExtensionPropertyInfo`. + /// + /// # Safety + /// + /// * `property_info_ptr` must be valid. + pub(crate) unsafe fn new_from_sys( + property_info_ptr: *mut sys::GDExtensionPropertyInfo, + ) -> Self { + let variant_type = VariantType::from_sys((*property_info_ptr).type_); + let property_name = StringName::new_from_string_sys((*property_info_ptr).name); + let hint_string = GString::new_from_string_sys((*property_info_ptr).hint_string); + let hint = PropertyHint::from_ord((*property_info_ptr).hint.to_owned() as i32); + let usage = PropertyUsageFlags::from_ord((*property_info_ptr).usage as u64); + + Self { + variant_type, + class_name: ClassName::none(), + property_name, + hint_info: PropertyHintInfo { hint, hint_string }, + usage, + } + } + /// Properly frees a `sys::GDExtensionPropertyInfo` created by [`into_owned_property_sys`](Self::into_owned_property_sys). /// /// # Safety diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index d9be74d5a..1713fa248 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -472,6 +472,7 @@ where pub mod cap { use super::*; use crate::builtin::{StringName, Variant}; + use crate::meta::PropertyInfo; use crate::obj::{Base, Bounds, Gd}; use std::any::Any; @@ -571,6 +572,13 @@ pub mod cap { fn __godot_property_get_revert(&self, property: StringName) -> Option; } + #[doc(hidden)] + #[cfg(since_api = "4.2")] + pub trait GodotValidateProperty: GodotClass { + #[doc(hidden)] + fn __godot_validate_property(&self, property: &mut PropertyInfo); + } + /// Auto-implemented for `#[godot_api] impl MyClass` blocks pub trait ImplementsGodotApi: GodotClass { #[doc(hidden)] diff --git a/godot-core/src/registry/callbacks.rs b/godot-core/src/registry/callbacks.rs index d8b19254c..4860586a2 100644 --- a/godot-core/src/registry/callbacks.rs +++ b/godot-core/src/registry/callbacks.rs @@ -13,6 +13,7 @@ use crate::builder::ClassBuilder; use crate::builtin::{StringName, Variant}; use crate::classes::Object; +use crate::meta::PropertyInfo; use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass}; use crate::registry::plugin::ErasedDynGd; use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted}; @@ -357,6 +358,28 @@ pub unsafe extern "C" fn property_get_revert( sys::conv::SYS_TRUE } + +/// # Safety +/// +/// - Must only be called by Godot as a callback for `validate_property` for a rust-defined class of type `T`. +/// - `sys_property_info` must be valid for the whole duration of this function call (ie - can't be freed nor consumed). +#[deny(unsafe_op_in_unsafe_fn)] +#[cfg(since_api = "4.2")] +pub unsafe extern "C" fn validate_property( + instance: sys::GDExtensionClassInstancePtr, + property_info_ptr: *mut sys::GDExtensionPropertyInfo, +) -> sys::GDExtensionBool { + // SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call. + let storage = unsafe { as_storage::(instance) }; + let instance = storage.get(); + + let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) }; + T::__godot_validate_property(&*instance, &mut property_info); + // SAFETY: property_info_ptr is still valid + unsafe { property_info.update(property_info_ptr) }; + + sys::conv::SYS_TRUE +} // ---------------------------------------------------------------------------------------------------------------------------------------------- // Safe, higher-level methods diff --git a/godot-core/src/registry/class.rs b/godot-core/src/registry/class.rs index 9fca57b0c..fde83af58 100644 --- a/godot-core/src/registry/class.rs +++ b/godot-core/src/registry/class.rs @@ -452,6 +452,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { user_property_get_revert_fn, #[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: _, + #[cfg(since_api = "4.2")] + validate_property_fn, }) => { c.user_register_fn = user_register_fn; @@ -476,6 +478,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { c.godot_params.free_property_list_func = user_free_property_list_fn; c.godot_params.property_can_revert_func = user_property_can_revert_fn; c.godot_params.property_get_revert_func = user_property_get_revert_fn; + c.godot_params.validate_property_func = validate_property_fn; c.user_virtual_fn = get_virtual_fn; } PluginItem::DynTraitImpl(dyn_trait_impl) => { diff --git a/godot-core/src/registry/plugin.rs b/godot-core/src/registry/plugin.rs index e4734dfa8..ab3a8f105 100644 --- a/godot-core/src/registry/plugin.rs +++ b/godot-core/src/registry/plugin.rs @@ -404,8 +404,15 @@ pub struct ITraitImpl { r_ret: sys::GDExtensionVariantPtr, ) -> sys::GDExtensionBool, >, -} + #[cfg(since_api = "4.2")] + pub(crate) validate_property_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_property: *mut sys::GDExtensionPropertyInfo, + ) -> sys::GDExtensionBool, + >, +} impl ITraitImpl { pub fn new( #[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: &'static str, @@ -485,6 +492,14 @@ impl ITraitImpl { ); self } + + pub fn with_validate_property(mut self) -> Self { + set( + &mut self.validate_property_fn, + callbacks::validate_property::, + ); + self + } } /// Representation of a `#[godot_dyn]` invocation. diff --git a/godot-macros/src/class/data_models/interface_trait_impl.rs b/godot-macros/src/class/data_models/interface_trait_impl.rs index 131ad3549..478cbf8c2 100644 --- a/godot-macros/src/class/data_models/interface_trait_impl.rs +++ b/godot-macros/src/class/data_models/interface_trait_impl.rs @@ -25,6 +25,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult ParseResult { + let inactive_class_early_return = make_inactive_class_check(quote! { false }); + validate_property_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotValidateProperty for #class_name { + fn __godot_validate_property(&self, property: &mut PropertyInfo) { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::validate_property(self, property); + } + } + }; + modifiers.push((cfg_attrs, ident("with_validate_property"))); + } + // Other virtual methods, like ready, process etc. method_name_str => { #[cfg(since_api = "4.4")] @@ -315,6 +334,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult = obj.get_property_list(); + + for property in properties.iter_shared() { + if property + .get("name") + .map(|v| v.to_string() == "SuperNewTestPropertyName") + .unwrap_or(false) + { + let Some(usage) = property.get("usage").map(|v| v.to::()) else { + continue; + }; + assert_eq!(usage, PropertyUsageFlags::NO_EDITOR); + obj.free(); + return; + } + } + + panic!("Test failed – unable to find validated property."); +}