Skip to content

Commit f929b27

Browse files
committed
Add "validate_property" virtual func
1 parent 6da1bbf commit f929b27

File tree

10 files changed

+207
-0
lines changed

10 files changed

+207
-0
lines changed

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

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

122+
#[cfg(since_api = "4.2")]
123+
fn validate_property(&self, property: &mut crate::meta::PropertyInfo) {
124+
unimplemented!()
125+
}
126+
122127
/// Called by Godot to tell if a property has a custom revert or not.
123128
///
124129
/// Return `None` for no custom revert, and return `Some(value)` to specify the custom revert.

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

+11
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ impl StringName {
143143
*boxed
144144
}
145145

146+
/// Moves this string into a string name sys pointer. This is the same as using [`GodotFfi::move_return_ptr`].
147+
///
148+
/// # Safety
149+
///
150+
/// `dst` must be a valid string name pointer.
151+
pub(crate) unsafe fn move_into_string_ptr(self, dst: sys::GDExtensionStringNamePtr) {
152+
let dst: sys::GDExtensionTypePtr = dst.cast();
153+
154+
self.move_return_ptr(dst, sys::PtrcallType::Standard);
155+
}
156+
146157
/// Convert a `StringName` sys pointer to a reference with unbounded lifetime.
147158
///
148159
/// # Safety

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

+49
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;
@@ -179,6 +180,54 @@ impl PropertyInfo {
179180
}
180181
}
181182

183+
/// Consumes self, moving the values into `sys::GDExtensionPropertyInfo` pointer.
184+
///
185+
/// # Safety
186+
///
187+
/// * `property_info_ptr` must be valid.
188+
pub(crate) unsafe fn move_into_property_info_ptr(
189+
self,
190+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
191+
) {
192+
(*property_info_ptr).type_ = self.variant_type.sys();
193+
(*property_info_ptr).hint = u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()");
194+
195+
self.hint_info
196+
.hint_string
197+
.move_into_string_ptr((*property_info_ptr).hint_string);
198+
self.property_name
199+
.move_into_string_ptr((*property_info_ptr).name);
200+
201+
(*property_info_ptr).usage = u32::try_from(self.usage.ord()).expect("usage.ord()");
202+
203+
if self.class_name != ClassName::none() {
204+
(*property_info_ptr).class_name = sys::SysPtr::force_mut(self.class_name.string_sys());
205+
}
206+
}
207+
208+
/// Creates copy of given `sys::GDExtensionPropertyInfo`.
209+
///
210+
/// # Safety
211+
///
212+
/// * `property_info_ptr` must be valid.
213+
pub(crate) unsafe fn new_from_sys(
214+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
215+
) -> Self {
216+
let variant_type = VariantType::from_sys((*property_info_ptr).type_);
217+
let property_name = StringName::new_from_string_sys((*property_info_ptr).name);
218+
let hint_string = GString::new_from_string_sys((*property_info_ptr).hint_string);
219+
let hint = PropertyHint::from_ord((*property_info_ptr).hint.to_owned() as i32);
220+
let usage = PropertyUsageFlags::from_ord((*property_info_ptr).usage as u64);
221+
222+
Self {
223+
variant_type,
224+
class_name: ClassName::none(),
225+
property_name,
226+
hint_info: PropertyHintInfo { hint, hint_string },
227+
usage,
228+
}
229+
}
230+
182231
/// Properly frees a `sys::GDExtensionPropertyInfo` created by [`into_owned_property_sys`](Self::into_owned_property_sys).
183232
///
184233
/// # Safety

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

+23
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,28 @@ pub unsafe extern "C" fn property_get_revert<T: cap::GodotPropertyGetRevert>(
357358

358359
sys::conv::SYS_TRUE
359360
}
361+
362+
/// # Safety
363+
///
364+
/// - Must only be called by Godot as a callback for `validate_property` for a rust-defined class of type `T`.
365+
/// - `sys_property_info` must be valid for the whole duration of this function call (ie - can't be freed nor consumed).
366+
#[deny(unsafe_op_in_unsafe_fn)]
367+
#[cfg(since_api = "4.2")]
368+
pub unsafe extern "C" fn validate_property<T: cap::GodotValidateProperty>(
369+
instance: sys::GDExtensionClassInstancePtr,
370+
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
371+
) -> sys::GDExtensionBool {
372+
// SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call.
373+
let storage = unsafe { as_storage::<T>(instance) };
374+
let instance = storage.get();
375+
376+
let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) };
377+
T::__godot_validate_property(&*instance, &mut property_info);
378+
// SAFETY: property_info_ptr is still valid
379+
unsafe { property_info.move_into_property_info_ptr(property_info_ptr) };
380+
381+
sys::conv::SYS_TRUE
382+
}
360383
// ----------------------------------------------------------------------------------------------------------------------------------------------
361384
// Safe, higher-level methods
362385

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

+7
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,11 @@ 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+
// Not present in GdExtension
483+
#[cfg(since_api = "4.2")]
484+
{
485+
c.godot_params.validate_property_func = validate_property_fn;
486+
}
480487
}
481488
PluginItem::DynTraitImpl(dyn_trait_impl) => {
482489
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")]
@@ -315,6 +334,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
315334
#set_property_impl
316335
#get_property_list_impl
317336
#property_get_revert_impl
337+
#validate_property_impl
318338

319339
impl ::godot::private::You_forgot_the_attribute__godot_api for #class_name {}
320340

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.
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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::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+
#[export]
20+
my_var: i64,
21+
}
22+
23+
#[godot_api]
24+
impl IObject for ValidatePropertyTest {
25+
fn validate_property(&self, property: &mut PropertyInfo) {
26+
if property.property_name.to_string() == "my_var" {
27+
property.usage = PropertyUsageFlags::NO_EDITOR;
28+
property.property_name = StringName::from("SuperNewTestPropertyName");
29+
property.hint_info.hint_string = GString::from("SomePropertyHint");
30+
31+
// Makes no sense but allows to check if given ClassName can be properly moved to GDExtensionPropertyInfo
32+
property.class_name = <ValidatePropertyTest as godot::obj::GodotClass>::class_name();
33+
}
34+
}
35+
}
36+
37+
#[itest]
38+
fn validate_property_test() {
39+
let obj = ValidatePropertyTest::new_alloc();
40+
let properties: Array<Dictionary> = obj.get_property_list();
41+
42+
for property in properties.iter_shared() {
43+
if property
44+
.get("name")
45+
.map(|v| v.to_string() == "SuperNewTestPropertyName")
46+
.unwrap_or(false)
47+
{
48+
let Some(usage) = property.get("usage").map(|v| v.to::<PropertyUsageFlags>()) else {
49+
continue;
50+
};
51+
assert_eq!(usage, PropertyUsageFlags::NO_EDITOR);
52+
53+
let Some(usage) = property.get("hint_string").map(|v| v.to::<GString>()) else {
54+
continue;
55+
};
56+
assert_eq!(usage, GString::from("SomePropertyHint"));
57+
58+
obj.free();
59+
return;
60+
}
61+
}
62+
63+
obj.free();
64+
panic!("Test failed – unable to find validated property.");
65+
}

0 commit comments

Comments
 (0)