Skip to content

Commit f6a309a

Browse files
authored
Merge pull request #1030 from Yarwin/add_validate_property_fn
Add "validate_property" virtual func
2 parents 80c24f6 + ed99757 commit f6a309a

File tree

12 files changed

+244
-1
lines changed

12 files changed

+244
-1
lines changed

Diff for: godot-codegen/src/generator/virtual_traits.rs

+12
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ fn make_special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
119119
unimplemented!()
120120
}
121121

122+
/// Called whenever Godot retrieves value of property. Allows to customize existing properties.
123+
/// Every property info goes through this method, except properties **added** with `get_property_list()`.
124+
///
125+
/// Exposed `property` here is a shared mutable reference obtained (and returned to) from Godot.
126+
///
127+
/// See also in the Godot docs:
128+
/// * [`Object::_validate_property`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-validate-property)
129+
#[cfg(since_api = "4.2")]
130+
fn validate_property(&self, property: &mut crate::meta::PropertyInfo) {
131+
unimplemented!()
132+
}
133+
122134
/// Called by Godot to tell if a property has a custom revert or not.
123135
///
124136
/// Return `None` for no custom revert, and return `Some(value)` to specify the custom revert.

Diff for: godot-core/src/builtin/string/gstring.rs

+11
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,17 @@ impl GString {
148148
*boxed
149149
}
150150

151+
/// Convert a `GString` sys pointer to a mutable reference with unbounded lifetime.
152+
///
153+
/// # Safety
154+
///
155+
/// - `ptr` must point to a live `GString` for the duration of `'a`.
156+
/// - Must be exclusive - no other reference to given `GString` instance can exist for the duration of `'a`.
157+
pub(crate) unsafe fn borrow_string_sys_mut<'a>(ptr: sys::GDExtensionStringPtr) -> &'a mut Self {
158+
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
159+
&mut *(ptr.cast::<GString>())
160+
}
161+
151162
/// Moves this string into a string sys pointer. This is the same as using [`GodotFfi::move_return_ptr`].
152163
///
153164
/// # Safety

Diff for: godot-core/src/builtin/string/string_name.rs

+13
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,19 @@ impl StringName {
155155
&*(ptr.cast::<StringName>())
156156
}
157157

158+
/// Convert a `StringName` sys pointer to a mutable reference with unbounded lifetime.
159+
///
160+
/// # Safety
161+
///
162+
/// - `ptr` must point to a live `StringName` for the duration of `'a`.
163+
/// - Must be exclusive - no other reference to given `StringName` instance can exist for the duration of `'a`.
164+
pub(crate) unsafe fn borrow_string_sys_mut<'a>(
165+
ptr: sys::GDExtensionStringNamePtr,
166+
) -> &'a mut StringName {
167+
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
168+
&mut *(ptr.cast::<StringName>())
169+
}
170+
158171
#[doc(hidden)]
159172
pub fn as_inner(&self) -> inner::InnerStringName {
160173
inner::InnerStringName::from_outer(self)

Diff for: godot-core/src/meta/property_info.rs

+47
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::global::{PropertyHint, PropertyUsageFlags};
1010
use crate::meta::{
1111
element_godot_type_name, ArrayElement, ClassName, GodotType, PackedArrayElement,
1212
};
13+
use crate::obj::{EngineBitfield, EngineEnum};
1314
use crate::registry::property::{Export, Var};
1415
use crate::sys;
1516
use godot_ffi::VariantType;
@@ -194,6 +195,52 @@ impl PropertyInfo {
194195
let _hint_string = GString::from_owned_string_sys(info.hint_string);
195196
}
196197
}
198+
199+
/// Moves its values into given `GDExtensionPropertyInfo`, dropping previous values if necessary.
200+
///
201+
/// # Safety
202+
///
203+
/// * `property_info_ptr` must be valid.
204+
///
205+
pub(crate) unsafe fn move_into_property_info_ptr(
206+
self,
207+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
208+
) {
209+
let ptr = &mut *property_info_ptr;
210+
211+
ptr.usage = u32::try_from(self.usage.ord()).expect("usage.ord()");
212+
ptr.hint = u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()");
213+
ptr.type_ = self.variant_type.sys();
214+
215+
*StringName::borrow_string_sys_mut(ptr.name) = self.property_name;
216+
*GString::borrow_string_sys_mut(ptr.hint_string) = self.hint_info.hint_string;
217+
218+
if self.class_name != ClassName::none() {
219+
*StringName::borrow_string_sys_mut(ptr.class_name) = self.class_name.to_string_name();
220+
}
221+
}
222+
223+
/// Creates copy of given `sys::GDExtensionPropertyInfo`.
224+
///
225+
/// # Safety
226+
///
227+
/// * `property_info_ptr` must be valid.
228+
pub(crate) unsafe fn new_from_sys(
229+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
230+
) -> Self {
231+
let ptr = *property_info_ptr;
232+
233+
Self {
234+
variant_type: VariantType::from_sys(ptr.type_),
235+
class_name: ClassName::none(),
236+
property_name: StringName::new_from_string_sys(ptr.name),
237+
hint_info: PropertyHintInfo {
238+
hint: PropertyHint::from_ord(ptr.hint.to_owned() as i32),
239+
hint_string: GString::new_from_string_sys(ptr.hint_string),
240+
},
241+
usage: PropertyUsageFlags::from_ord(ptr.usage as u64),
242+
}
243+
}
197244
}
198245

199246
// ----------------------------------------------------------------------------------------------------------------------------------------------

Diff for: godot-core/src/obj/traits.rs

+8
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ where
472472
pub mod cap {
473473
use super::*;
474474
use crate::builtin::{StringName, Variant};
475+
use crate::meta::PropertyInfo;
475476
use crate::obj::{Base, Bounds, Gd};
476477
use std::any::Any;
477478

@@ -571,6 +572,13 @@ pub mod cap {
571572
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
572573
}
573574

575+
#[doc(hidden)]
576+
#[cfg(since_api = "4.2")]
577+
pub trait GodotValidateProperty: GodotClass {
578+
#[doc(hidden)]
579+
fn __godot_validate_property(&self, property: &mut PropertyInfo);
580+
}
581+
574582
/// Auto-implemented for `#[godot_api] impl MyClass` blocks
575583
pub trait ImplementsGodotApi: GodotClass {
576584
#[doc(hidden)]

Diff for: godot-core/src/registry/callbacks.rs

+30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use crate::builder::ClassBuilder;
1414
use crate::builtin::{StringName, Variant};
1515
use crate::classes::Object;
16+
use crate::meta::PropertyInfo;
1617
use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass};
1718
use crate::registry::plugin::ErasedDynGd;
1819
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
@@ -357,6 +358,35 @@ pub unsafe extern "C" fn property_get_revert<T: cap::GodotPropertyGetRevert>(
357358

358359
sys::conv::SYS_TRUE
359360
}
361+
362+
/// Callback for `validate_property`.
363+
///
364+
/// Exposes `PropertyInfo` created out of `*mut GDExtensionPropertyInfo` ptr to user and moves edited values back to the pointer.
365+
///
366+
/// # Safety
367+
///
368+
/// - Must only be called by Godot as a callback for `validate_property` for a rust-defined class of type `T`.
369+
/// - `property_info_ptr` must be valid for the whole duration of this function call (i.e. - can't be freed nor consumed).
370+
///
371+
#[deny(unsafe_op_in_unsafe_fn)]
372+
#[cfg(since_api = "4.2")]
373+
pub unsafe extern "C" fn validate_property<T: cap::GodotValidateProperty>(
374+
instance: sys::GDExtensionClassInstancePtr,
375+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
376+
) -> sys::GDExtensionBool {
377+
// SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call.
378+
let storage = unsafe { as_storage::<T>(instance) };
379+
let instance = storage.get();
380+
381+
// SAFETY: property_info_ptr must be valid.
382+
let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) };
383+
T::__godot_validate_property(&*instance, &mut property_info);
384+
385+
// SAFETY: property_info_ptr remains valid & unchanged.
386+
unsafe { property_info.move_into_property_info_ptr(property_info_ptr) };
387+
388+
sys::conv::SYS_TRUE
389+
}
360390
// ----------------------------------------------------------------------------------------------------------------------------------------------
361391
// Safe, higher-level methods
362392

Diff for: godot-core/src/registry/class.rs

+6
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
452452
user_property_get_revert_fn,
453453
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
454454
virtual_method_docs: _,
455+
#[cfg(since_api = "4.2")]
456+
validate_property_fn,
455457
}) => {
456458
c.user_register_fn = user_register_fn;
457459

@@ -477,6 +479,10 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
477479
c.godot_params.property_can_revert_func = user_property_can_revert_fn;
478480
c.godot_params.property_get_revert_func = user_property_get_revert_fn;
479481
c.user_virtual_fn = get_virtual_fn;
482+
#[cfg(since_api = "4.2")]
483+
{
484+
c.godot_params.validate_property_func = validate_property_fn;
485+
}
480486
}
481487
PluginItem::DynTraitImpl(dyn_trait_impl) => {
482488
let type_id = dyn_trait_impl.dyn_trait_typeid();

Diff for: godot-core/src/registry/plugin.rs

+16
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,13 @@ pub struct ITraitImpl {
404404
r_ret: sys::GDExtensionVariantPtr,
405405
) -> sys::GDExtensionBool,
406406
>,
407+
#[cfg(since_api = "4.2")]
408+
pub(crate) validate_property_fn: Option<
409+
unsafe extern "C" fn(
410+
p_instance: sys::GDExtensionClassInstancePtr,
411+
p_property: *mut sys::GDExtensionPropertyInfo,
412+
) -> sys::GDExtensionBool,
413+
>,
407414
}
408415

409416
impl ITraitImpl {
@@ -485,6 +492,15 @@ impl ITraitImpl {
485492
);
486493
self
487494
}
495+
496+
#[cfg(since_api = "4.2")]
497+
pub fn with_validate_property<T: GodotClass + cap::GodotValidateProperty>(mut self) -> Self {
498+
set(
499+
&mut self.validate_property_fn,
500+
callbacks::validate_property::<T>,
501+
);
502+
self
503+
}
488504
}
489505

490506
/// Representation of a `#[godot_dyn]` invocation.

Diff for: godot-macros/src/class/data_models/interface_trait_impl.rs

+20
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
2525
let mut set_property_impl = TokenStream::new();
2626
let mut get_property_list_impl = TokenStream::new();
2727
let mut property_get_revert_impl = TokenStream::new();
28+
let mut validate_property_impl = TokenStream::new();
2829

2930
let mut modifiers = Vec::new();
3031

@@ -207,6 +208,24 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
207208
modifiers.push((cfg_attrs, ident("with_property_get_revert")));
208209
}
209210

211+
#[cfg(since_api = "4.2")]
212+
"validate_property" => {
213+
let inactive_class_early_return = make_inactive_class_check(TokenStream::new());
214+
validate_property_impl = quote! {
215+
#(#cfg_attrs)*
216+
impl ::godot::obj::cap::GodotValidateProperty for #class_name {
217+
fn __godot_validate_property(&self, property: &mut ::godot::meta::PropertyInfo) {
218+
use ::godot::obj::UserClass as _;
219+
220+
#inactive_class_early_return
221+
222+
<Self as #trait_path>::validate_property(self, property);
223+
}
224+
}
225+
};
226+
modifiers.push((cfg_attrs, ident("with_validate_property")));
227+
}
228+
210229
// Other virtual methods, like ready, process etc.
211230
method_name_str => {
212231
#[cfg(since_api = "4.4")]
@@ -317,6 +336,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
317336
#set_property_impl
318337
#get_property_list_impl
319338
#property_get_revert_impl
339+
#validate_property_impl
320340

321341
impl ::godot::private::You_forgot_the_attribute__godot_api for #class_name {}
322342

Diff for: itest/rust/src/object_tests/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ mod property_template_test;
2323
mod property_test;
2424
mod reentrant_test;
2525
mod singleton_test;
26+
// `validate_property` is only supported in Godot 4.2+.
27+
#[cfg(since_api = "4.2")]
28+
mod validate_property_test;
2629
mod virtual_methods_test;
2730

2831
// Need to test this in the init level method.

Diff for: itest/rust/src/object_tests/object_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use godot::sys::{self, interface_fn, GodotFfi};
2626
use crate::framework::{expect_panic, itest, TestContext};
2727

2828
// TODO:
29-
// * make sure that ptrcalls are used when possible (ie. when type info available; maybe GDScript integration test)
29+
// * make sure that ptrcalls are used when possible (i.e. when type info available; maybe GDScript integration test)
3030
// * Deref impl for user-defined types
3131

3232
#[itest]
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 godot::builtin::{Array, Dictionary, GString, StringName};
9+
use godot::classes::IObject;
10+
use godot::global::{PropertyHint, PropertyUsageFlags};
11+
use godot::meta::PropertyInfo;
12+
use godot::obj::NewAlloc;
13+
use godot::register::{godot_api, GodotClass};
14+
use godot::test::itest;
15+
16+
#[derive(GodotClass)]
17+
#[class(base = Object, init)]
18+
pub struct ValidatePropertyTest {
19+
#[var(hint = NONE, hint_string = "initial")]
20+
#[export]
21+
my_var: i64,
22+
}
23+
24+
#[godot_api]
25+
impl IObject for ValidatePropertyTest {
26+
fn validate_property(&self, property: &mut PropertyInfo) {
27+
if property.property_name.to_string() == "my_var" {
28+
property.usage = PropertyUsageFlags::NO_EDITOR;
29+
property.property_name = StringName::from("SuperNewTestPropertyName");
30+
property.hint_info.hint_string = GString::from("SomePropertyHint");
31+
property.hint_info.hint = PropertyHint::TYPE_STRING;
32+
33+
// Makes no sense, but allows to check if given ClassName can be properly moved to GDExtensionPropertyInfo.
34+
property.class_name = <ValidatePropertyTest as godot::obj::GodotClass>::class_name();
35+
}
36+
}
37+
}
38+
39+
#[itest]
40+
fn validate_property_test() {
41+
let obj = ValidatePropertyTest::new_alloc();
42+
let properties: Array<Dictionary> = obj.get_property_list();
43+
44+
let property = properties
45+
.iter_shared()
46+
.find(|dict| {
47+
dict.get("name")
48+
.is_some_and(|v| v.to_string() == "SuperNewTestPropertyName")
49+
})
50+
.expect("Test failed – unable to find validated property.");
51+
52+
let hint_string = property
53+
.get("hint_string")
54+
.expect("validated property dict should contain a `hint_string` entry.")
55+
.to::<GString>();
56+
assert_eq!(hint_string, GString::from("SomePropertyHint"));
57+
58+
let class = property
59+
.get("class_name")
60+
.expect("Validated property dict should contain a class_name entry.")
61+
.to::<StringName>();
62+
assert_eq!(class, StringName::from("ValidatePropertyTest"));
63+
64+
let usage = property
65+
.get("usage")
66+
.expect("Validated property dict should contain an usage entry.")
67+
.to::<PropertyUsageFlags>();
68+
assert_eq!(usage, PropertyUsageFlags::NO_EDITOR);
69+
70+
let hint = property
71+
.get("hint")
72+
.expect("Validated property dict should contain a hint entry.")
73+
.to::<PropertyHint>();
74+
assert_eq!(hint, PropertyHint::TYPE_STRING);
75+
76+
obj.free();
77+
}

0 commit comments

Comments
 (0)