Skip to content

Commit

Permalink
Merge pull request godot-rust#991 from godot-rust/feature/compat-virt…
Browse files Browse the repository at this point in the history
…ual-methods

Godot FFI: postinit create, compat virtual methods, icon paths
  • Loading branch information
Yarwin committed Jan 31, 2025
1 parent 6da1bbf commit ac4d9ec
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 1 deletion.
5 changes: 5 additions & 0 deletions godot-codegen/src/generator/virtual_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
38 changes: 38 additions & 0 deletions godot-core/src/meta/property_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

#[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)]
Expand Down
23 changes: 23 additions & 0 deletions godot-core/src/registry/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -357,6 +358,28 @@ pub unsafe extern "C" fn property_get_revert<T: cap::GodotPropertyGetRevert>(

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<T: cap::GodotValidateProperty>(
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::<T>(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

Expand Down
3 changes: 3 additions & 0 deletions godot-core/src/registry/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) => {
Expand Down
17 changes: 16 additions & 1 deletion godot-core/src/registry/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: GodotClass + cap::ImplementsGodotVirtual>(
#[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: &'static str,
Expand Down Expand Up @@ -485,6 +492,14 @@ impl ITraitImpl {
);
self
}

pub fn with_validate_property<T: GodotClass + cap::GodotValidateProperty>(mut self) -> Self {
set(
&mut self.validate_property_fn,
callbacks::validate_property::<T>,
);
self
}
}

/// Representation of a `#[godot_dyn]` invocation.
Expand Down
20 changes: 20 additions & 0 deletions godot-macros/src/class/data_models/interface_trait_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
let mut set_property_impl = TokenStream::new();
let mut get_property_list_impl = TokenStream::new();
let mut property_get_revert_impl = TokenStream::new();
let mut validate_property_impl = TokenStream::new();

let mut modifiers = Vec::new();

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

#[cfg(since_api = "4.2")]
"validate_property" => {
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

<Self as #trait_path>::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")]
Expand Down Expand Up @@ -315,6 +334,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
#set_property_impl
#get_property_list_impl
#property_get_revert_impl
#validate_property_impl

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

Expand Down
1 change: 1 addition & 0 deletions itest/rust/src/object_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod property_template_test;
mod property_test;
mod reentrant_test;
mod singleton_test;
mod validate_property_test;
mod virtual_methods_test;

// Need to test this in the init level method.
Expand Down
54 changes: 54 additions & 0 deletions itest/rust/src/object_tests/validate_property_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use godot::builtin::{Array, Dictionary, StringName};
use godot::classes::IObject;
use godot::global::PropertyUsageFlags;
use godot::meta::PropertyInfo;
use godot::obj::NewAlloc;
use godot::register::{godot_api, GodotClass};
use godot::test::itest;

#[derive(GodotClass)]
#[class(base = Object, init)]
pub struct ValidatePropertyTest {
#[export]
my_var: i64,
}

#[godot_api]
impl IObject for ValidatePropertyTest {
fn validate_property(&self, property: &mut PropertyInfo) {
if property.property_name.to_string() == "my_var" {
property.usage = PropertyUsageFlags::NO_EDITOR;
property.property_name = StringName::from("SuperNewTestPropertyName");
}
}
}

#[itest]
fn validate_property_test() {
let obj = ValidatePropertyTest::new_alloc();
let properties: Array<Dictionary> = 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::<PropertyUsageFlags>()) else {
continue;
};
assert_eq!(usage, PropertyUsageFlags::NO_EDITOR);
obj.free();
return;
}
}

panic!("Test failed – unable to find validated property.");
}

0 comments on commit ac4d9ec

Please sign in to comment.