Skip to content

Add get_property_list, property_can_revert and property_get_revert virtual methods #621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions godot-codegen/src/generator/virtual_traits.rs
Original file line number Diff line number Diff line change
@@ -104,6 +104,43 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
unimplemented!()
}

/// Called whenever the Godot engine needs to determine the properties an object has.
///
/// This method can be used to dynamically update the properties displayed by the editor depending on various conditions. This should
/// usually be combined with `#[class(tool)]` so that the code actually runs in the editor. Additionally if the property list changes
/// you need to call [`notify_property_list_changed`](crate::engine::Object::notify_property_list_changed) to actually notify the engine
/// that the property list has changed, otherwise nothing will appear to have happened.
///
/// The returned `Vec` cannot have more than `u32::MAX` or `usize::MAX - 1` elements, whichever is smaller.
///
/// See also in Godot docs:
/// * [`Object::_get_property_list`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-get-property-list).
fn get_property_list(&self) -> Vec<PropertyInfo> {
unimplemented!()
}

/// Called by Godot to determine if a property has a custom default value.
///
/// Should return `true` when the property has a custom default value, otherwise should return `false`. Must be used in conjunction with
/// [`property_get_revert()`] to specify the custom default value.
///
/// See also in Godot docs:
/// * [`Object::_property_can_revert`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-property-can-revert).
fn property_can_revert(&self, property: StringName) -> bool {
unimplemented!()
}

/// Called by Godot to determine the custom default value of a property.
///
/// Should return the given property's custom default value as `Some(value)`, or `None` if the given property doesn't have a custom
/// default value.
///
/// See also in Godot docs:
/// * [`Object::_property_get_revert`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-property-get-revert).
fn property_get_revert(&self, property: StringName) -> Option<Variant> {
unimplemented!()
}

}
}

2 changes: 1 addition & 1 deletion godot-codegen/src/util.rs
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ pub fn make_imports() -> TokenStream {
quote! {
use godot_ffi as sys;
use crate::builtin::*;
use crate::builtin::meta::{ClassName, PtrcallReturnUnit, PtrcallReturnT, PtrcallReturnOptionGdT, PtrcallSignatureTuple, VarcallSignatureTuple};
use crate::builtin::meta::{ClassName, PtrcallReturnUnit, PtrcallReturnT, PtrcallReturnOptionGdT, PtrcallSignatureTuple, VarcallSignatureTuple, PropertyInfo};
use crate::engine::native::*;
use crate::engine::Object;
use crate::obj::Gd;
117 changes: 114 additions & 3 deletions godot-core/src/builtin/meta/mod.rs
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ pub(crate) use godot_convert::convert_error::*;

use crate::builtin::*;
use crate::engine::global;
use crate::property::{Export, PropertyHintInfo, Var};
use godot_ffi as sys;
use registration::method::MethodParamOrReturnInfo;
use sys::{GodotFfi, GodotNullableFfi};
@@ -241,21 +242,93 @@ where

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Rusty abstraction of `sys::GDExtensionPropertyInfo`.
/// Representation of a property in Godot.
///
/// Keeps the actual allocated values (the `sys` equivalent only keeps pointers, which fall out of scope).
/// Stores all info needed to inform Godot about how to interpret a property. This is used for actual properties, function arguments,
/// return types, signal arguments, and other similar cases.
///
/// A mismatch between property info and the actual type of a property may lead to runtime errors as Godot tries to use the property in the
/// wrong way, such as by inserting the wrong type or expecting a different type to be returned.
///
/// Rusty abstraction of `sys::GDExtensionPropertyInfo`, keeps the actual allocated values (the `sys` equivalent only keeps pointers, which
/// fall out of scope).
#[derive(Debug)]
// Note: is not #[non_exhaustive], so adding fields is a breaking change. Mostly used internally at the moment though.
// It is uncertain if we want to add more fields to this in the future, so we'll mark it `non_exhaustive` as a precautionary measure.
#[non_exhaustive]
pub struct PropertyInfo {
/// The variant type of the property.
///
/// Note that for classes this will be `Object`, and the `class_name` field will specify what specific class this property is.
pub variant_type: VariantType,
/// The class name of the property.
///
/// This only matters if `variant_type` is `Object`. Otherwise it's ignored by Godot.
pub class_name: ClassName,
/// The name this property will have in Godot.
pub property_name: StringName,
/// The property hint that will determine how Godot interprets this value.
///
/// See Godot docs for more information:
/// * [`PropertyHint`](https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-propertyhint).
pub hint: global::PropertyHint,
/// Extra information used in conjunction with `hint`.
pub hint_string: GString,
/// How Godot will use this property.
///
/// See Godot docs for more inormation:
/// * [`PropertyUsageFlags`](https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-propertyusageflags).
pub usage: global::PropertyUsageFlags,
}

impl PropertyInfo {
/// Create a new `PropertyInfo` for a property that isn't exported to the editor.
///
/// `P` is the type the property will be declared as, and `property_name` is the name the property will have.
pub fn new_var<P: Var>(property_name: &str) -> Self {
let PropertyHintInfo { hint, hint_string } = P::property_hint();

Self {
hint,
hint_string,
usage: global::PropertyUsageFlags::NO_EDITOR,
..P::Via::property_info(property_name)
}
}

/// Create a new `PropertyInfo` for a property that is exported to the editor.
///
/// `P` is the type the property will be declared as, and `property_name` is the name the property will have.
pub fn new_export<P: Export>(property_name: &str) -> Self {
let PropertyHintInfo { hint, hint_string } = P::default_export_info();

Self {
hint,
hint_string,
usage: global::PropertyUsageFlags::DEFAULT,
..P::Via::property_info(property_name)
}
}

/// Create a new `PropertyInfo` for the return type of a method.
///
/// `P` is the type the property will be declared as.
pub fn new_return<P: ToGodot>() -> Self {
Self {
usage: global::PropertyUsageFlags::NONE,
..P::Via::property_info("")
}
}

/// Create a new `PropertyInfo` for an argument of a method.
///
/// `P` is the type the property will be declared as, and `property_name` is the name the argument will have.
pub fn new_arg<P: FromGodot>(arg_name: &str) -> Self {
Self {
usage: global::PropertyUsageFlags::NONE,
..P::Via::property_info(arg_name)
}
}

/// Converts to the FFI type. Keep this object allocated while using that!
pub fn property_sys(&self) -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
@@ -271,6 +344,44 @@ impl PropertyInfo {
}
}

/// Converts to the FFI type.
///
/// Unlike [`property_sys`](PropertyInfo::property_sys) this object does not need to be kept allocated while using the returned value,
/// however if you do not explicitly free the returned value at some point then this will lead to a memory leak. See
/// [`drop_property_sys`](PropertyInfo::drop_property_sys).
pub fn into_property_sys(self) -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
use crate::obj::EngineEnum as _;

let Self {
variant_type,
class_name,
property_name,
hint,
hint_string,
usage,
} = self;

sys::GDExtensionPropertyInfo {
type_: variant_type.sys(),
name: property_name.into_string_sys(),
class_name: class_name.string_sys(),
hint: u32::try_from(hint.ord()).expect("hint.ord()"),
hint_string: hint_string.into_string_sys(),
usage: u32::try_from(usage.ord()).expect("usage.ord()"),
}
}

/// Consumes a [sys::GDExtensionPropertyInfo].
///
/// # Safety
///
/// The given property info must have been returned from a call to [`into_property_sys`](PropertyInfo::into_property_sys).
pub unsafe fn drop_property_sys(property_sys: sys::GDExtensionPropertyInfo) {
let _property_name = StringName::from_string_sys(property_sys.name);
let _hint_string = GString::from_string_sys(property_sys.hint_string);
}

pub fn empty_sys() -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
use crate::obj::EngineEnum as _;
6 changes: 6 additions & 0 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
@@ -142,6 +142,12 @@ impl GString {
pub fn as_inner(&self) -> inner::InnerString {
inner::InnerString::from_outer(self)
}

#[doc(hidden)]
pub fn into_string_sys(self) -> sys::GDExtensionStringPtr {
let string_name = Box::leak(Box::new(self));
string_name.string_sys()
}
}

// SAFETY:
6 changes: 6 additions & 0 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
@@ -130,6 +130,12 @@ impl StringName {
inner::InnerStringName::from_outer(self)
}

#[doc(hidden)]
pub fn into_string_sys(self) -> sys::GDExtensionStringNamePtr {
let string_name = Box::leak(Box::new(self));
string_name.string_sys()
}

/// Increment ref-count. This may leak memory if used wrongly.
fn inc_ref(&self) {
std::mem::forget(self.clone());
19 changes: 19 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
@@ -406,6 +406,7 @@ where
/// Capability traits, providing dedicated functionalities for Godot classes
pub mod cap {
use super::*;
use crate::builtin::meta::PropertyInfo;
use crate::builtin::{StringName, Variant};
use crate::obj::{Base, Bounds, Gd};

@@ -486,6 +487,24 @@ pub mod cap {
fn __godot_set_property(&mut self, property: StringName, value: Variant) -> bool;
}

#[doc(hidden)]
pub trait GodotGetPropertyList: GodotClass {
#[doc(hidden)]
fn __godot_get_property_list(&self) -> Vec<PropertyInfo>;
}

#[doc(hidden)]
pub trait GodotPropertyCanRevert: GodotClass {
#[doc(hidden)]
fn __godot_property_can_revert(&self, property: StringName) -> bool;
}

#[doc(hidden)]
pub trait GodotPropertyGetRevert: GodotClass {
#[doc(hidden)]
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
}

/// Auto-implemented for `#[godot_api] impl MyClass` blocks
pub trait ImplementsGodotApi: GodotClass {
#[doc(hidden)]
157 changes: 157 additions & 0 deletions godot-core/src/registry/callbacks.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
#![allow(clippy::missing_safety_doc)]

use crate::builder::ClassBuilder;
use crate::builtin::meta::PropertyInfo;
use crate::builtin::{StringName, Variant};
use crate::obj::{cap, Base, GodotClass, UserClass};
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
@@ -198,6 +199,162 @@ pub unsafe extern "C" fn set_property<T: cap::GodotSet>(
T::__godot_set_property(&mut *instance, property, value) as sys::GDExtensionBool
}

pub unsafe extern "C" fn get_property_list<T: cap::GodotGetPropertyList>(
instance: sys::GDExtensionClassInstancePtr,
count: *mut u32,
) -> *const sys::GDExtensionPropertyInfo {
let storage = as_storage::<T>(instance);
let instance = storage.get();

let property_infos = T::__godot_get_property_list(&*instance);
let property_count: u32 = property_infos
.len()
.try_into()
.expect("cannot pass more properties than `u32::MAX`");
let vec_length: usize = property_count
.try_into()
.expect("gdext does not support targets with `u32` bigger than `usize`");
// This can only fail if vec_length = u32::MAX and usize is the same size as u32.
let vec_length = vec_length
.checked_add(1)
.expect("cannot pass more properties than `usize::MAX - 1`");

// Use `ManuallyDrop` here so we intentionally leak this vec, as we want to pass ownership of this array to Godot until ownership is
// returned to us in `free_property_list`.
let mut list = Vec::with_capacity(vec_length);
list.extend(
property_infos
.into_iter()
.map(PropertyInfo::into_property_sys),
);

// Use as null-terminator. `PropertyInfo::into_property_sys` will never create a `sys::GDExtensionPropertyInfo` with values like this.
// This is *important*. If we do have a value before the final one that is equal to `empty_sys()` then we would later call
// `Vec::from_raw_parts` with an incorrect capacity and trigger UB.
list.push(PropertyInfo::empty_sys());

// So at least in debug mode, let's check that our assumptions about `list` hold true.
if cfg!(debug_assertions) {
for prop in list.iter().take(vec_length - 1) {
assert!(
!prop.name.is_null(),
"Invalid property info found: {:?}",
prop
);
}
assert_eq!(list.len(), vec_length);
assert_eq!(list.capacity(), vec_length);
assert!((property_count as usize) < vec_length)
}

// SAFETY: Godot gives us exclusive ownership over `count` for the purposes of returning the length of the property list, so we can safely
// write a value of type `u32` to `count`.
unsafe {
count.write(property_count);
}

let slice = Box::into_raw(list.into_boxed_slice());

// Since `list` is in a `ManuallyDrop`, this leaks the `list` and thus passes ownership of the vec to the caller (which is gonna be Godot).
(*slice).as_mut_ptr() as *const _
}

/// Get the length of a "null"-terminated array.
///
/// Where "null" here is defined by `terminator_fn` returning true.
///
/// # Panics
///
/// The given array has more than `isize::MAX` elements.
///
/// # Safety
///
/// `arr` must be dereferencable to `&T`.
///
/// Whenever `terminator_fn(&*arr.offset(i))` is false, for every i in `0..n`, then:
/// - `arr.offset(n)` must be safe, see safety docs for `offset`.
/// - `arr.offset(n)` must be dereferencable to `&T`.
unsafe fn arr_length<T>(arr: *const T, terminator_fn: impl Fn(&T) -> bool) -> usize {
let mut list_index = 0;
loop {
// SAFETY: `terminator_fn` has not returned `true` yet, therefore we are allowed to do `arr.offset(list_index)`.
let arr_offset = unsafe { arr.offset(list_index) };

// SAFETY: `terminator_fn` has not returned `true` yet, therefore we can dereference `arr_offset` to `&T`.
let elem = unsafe { &*arr_offset };

if terminator_fn(elem) {
break;
}

list_index = list_index
.checked_add(1)
.expect("there should not be more than `isize::MAX` elements in the array");
}

usize::try_from(list_index).expect("the size of the array should never be negative") + 1
}

pub unsafe extern "C" fn free_property_list<T: cap::GodotGetPropertyList>(
_instance: sys::GDExtensionClassInstancePtr,
list: *const sys::GDExtensionPropertyInfo,
) {
// SAFETY: `list` was created in `get_property_list` from a `Vec` with some fixed length, where the final element is a
// `sys::GDExtensionPropertyInfo` with a null `name` field. So all the given safety conditions hold.
let list_length = unsafe { arr_length(list, |prop| prop.name.is_null()) };

// SAFETY: `list` was created in `get_property_list` from a `Vec` with length `list_length`. The length and capacity of this list
// are the same, as the vec was made using `with_capacity` to have exactly the same capacity as the amount of elements we put in the vec.
let v = unsafe { Vec::from_raw_parts(sys::force_mut_ptr(list), list_length, list_length) };

for prop in v.into_iter().take(list_length - 1) {
// SAFETY: All elements of `v` were created using `into_property_sys`, except for the last one. We are iterating over all elements
// except the last one, so all `prop` values were created with `into_property_sys`.
unsafe { crate::builtin::meta::PropertyInfo::drop_property_sys(prop) }
}
}

pub unsafe extern "C" fn property_can_revert<T: cap::GodotPropertyCanRevert>(
instance: sys::GDExtensionClassInstancePtr,
name: sys::GDExtensionConstStringNamePtr,
) -> sys::GDExtensionBool {
let storage = as_storage::<T>(instance);
let instance = storage.get();

let property = StringName::from_string_sys(sys::force_mut_ptr(name));

std::mem::forget(property.clone());

T::__godot_property_can_revert(&*instance, property) as sys::GDExtensionBool
}

pub unsafe extern "C" fn user_property_get_revert_fn<T: cap::GodotPropertyGetRevert>(
instance: sys::GDExtensionClassInstancePtr,
name: sys::GDExtensionConstStringNamePtr,
ret: sys::GDExtensionVariantPtr,
) -> sys::GDExtensionBool {
let storage = as_storage::<T>(instance);
let instance = storage.get();

let property = StringName::from_string_sys(sys::force_mut_ptr(name));

std::mem::forget(property.clone());

let value = T::__godot_property_get_revert(&*instance, property);

match value {
Some(value) => {
// SAFETY: `ret` is a pointer Godot has given us exclusive ownership over for the purpose of writing a `Variant` to when we return
// `true` from this function. So this write is safe.
unsafe {
value.move_var_ptr(ret);
}
true as sys::GDExtensionBool
}
None => false as sys::GDExtensionBool,
}
}

pub unsafe extern "C" fn reference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
let storage = as_storage::<T>(instance);
storage.on_inc_ref();
37 changes: 37 additions & 0 deletions godot-core/src/registry/mod.rs
Original file line number Diff line number Diff line change
@@ -176,6 +176,35 @@ pub enum PluginItem {
) -> sys::GDExtensionBool,
>,

user_get_property_list_fn: Option<
unsafe extern "C" fn(
p_instance: sys::GDExtensionClassInstancePtr,
r_count: *mut u32,
) -> *const sys::GDExtensionPropertyInfo,
>,

user_free_property_list_fn: Option<
unsafe extern "C" fn(
p_instance: sys::GDExtensionClassInstancePtr,
p_list: *const sys::GDExtensionPropertyInfo,
),
>,

user_property_can_revert_fn: Option<
unsafe extern "C" fn(
p_instance: sys::GDExtensionClassInstancePtr,
p_name: sys::GDExtensionConstStringNamePtr,
) -> sys::GDExtensionBool,
>,

user_property_get_revert_fn: Option<
unsafe extern "C" fn(
p_instance: sys::GDExtensionClassInstancePtr,
p_name: sys::GDExtensionConstStringNamePtr,
r_ret: sys::GDExtensionVariantPtr,
) -> sys::GDExtensionBool,
>,

/// Callback for other virtuals.
get_virtual_fn: unsafe extern "C" fn(
p_userdata: *mut std::os::raw::c_void,
@@ -446,6 +475,10 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
user_on_notification_fn,
user_set_fn,
user_get_fn,
user_get_property_list_fn: user_get_property_list,
user_free_property_list_fn: user_free_property_list,
user_property_can_revert_fn: user_property_can_revert,
user_property_get_revert_fn: user_property_get_revert,
get_virtual_fn,
} => {
c.user_register_fn = user_register_fn;
@@ -467,6 +500,10 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
c.godot_params.notification_func = user_on_notification_fn;
c.godot_params.set_func = user_set_fn;
c.godot_params.get_func = user_get_fn;
c.godot_params.get_property_list_func = user_get_property_list;
c.godot_params.free_property_list_func = user_free_property_list;
c.godot_params.property_can_revert_func = user_property_can_revert;
c.godot_params.property_get_revert_func = user_property_get_revert;
c.user_virtual_fn = Some(get_virtual_fn);
}
}
3 changes: 3 additions & 0 deletions godot-core/src/storage/instance_storage.rs
Original file line number Diff line number Diff line change
@@ -57,6 +57,9 @@ impl AtomicLifecycle {
///
/// It must be safe to drop this storage if we have a `&mut` reference to the storage and
/// [`is_bound()`](Storage::is_bound()) returns `false`.
///
/// The pointer returned from [`store_property_list`](Storage::store_property_list) must be valid for reads until
/// [`free_property_list`](Storage::free_property_list) is called.
pub unsafe trait Storage {
/// The type of instances stored by this storage.
type Instance: GodotClass;
14 changes: 6 additions & 8 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
@@ -157,14 +157,12 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
let (hint, hint_string) = #hint;
let usage = #usage_flags;

let property_info = ::godot::builtin::meta::PropertyInfo {
variant_type: #field_variant_type,
class_name: #field_class_name,
property_name: #field_name.into(),
hint,
hint_string,
usage,
};
let mut property_info = ::godot::builtin::meta::PropertyInfo::new_var::<#field_type>(#field_name);
property_info.variant_type = #field_variant_type;
property_info.class_name = #field_class_name;
property_info.hint = hint;
property_info.hint_string = hint_string;
property_info.usage = usage;

let getter_name = ::godot::builtin::StringName::from(#getter_name);
let setter_name = ::godot::builtin::StringName::from(#setter_name);
92 changes: 92 additions & 0 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
@@ -611,6 +611,9 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
let mut on_notification_impl = TokenStream::new();
let mut get_property_impl = TokenStream::new();
let mut set_property_impl = TokenStream::new();
let mut get_property_list_impl = TokenStream::new();
let mut property_can_revert_impl = TokenStream::new();
let mut property_get_revert_impl = TokenStream::new();

let mut register_fn = None;
let mut create_fn = None;
@@ -619,6 +622,10 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
let mut on_notification_fn = None;
let mut get_property_fn = None;
let mut set_property_fn = None;
let mut get_property_list_fn = None;
let mut free_property_list_fn = None;
let mut property_can_revert_fn = None;
let mut property_get_revert_fn = None;

let mut virtual_methods = vec![];
let mut virtual_method_cfg_attrs = vec![];
@@ -793,6 +800,80 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
});
}

"get_property_list" => {
get_property_list_impl = quote! {
#(#cfg_attrs)*
impl ::godot::obj::cap::GodotGetPropertyList for #class_name {
fn __godot_get_property_list(&self) -> Vec<::godot::builtin::meta::PropertyInfo> {
use ::godot::obj::UserClass as _;

#[cfg(before_api = "4.3")]
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
return Vec::new();
}

<Self as #trait_path>::get_property_list(self)
}
}
};

get_property_list_fn = Some(quote! {
#(#cfg_attrs)*
() => Some(#prv::callbacks::get_property_list::<#class_name>),
});

free_property_list_fn = Some(quote! {
#(#cfg_attrs)*
() => Some(#prv::callbacks::free_property_list::<#class_name>),
});
}

"property_can_revert" => {
property_can_revert_impl = quote! {
#(#cfg_attrs)*
impl ::godot::obj::cap::GodotPropertyCanRevert for #class_name {
fn __godot_property_can_revert(&self, property: ::godot::builtin::StringName) -> bool {
use ::godot::obj::UserClass as _;

#[cfg(before_api = "4.3")]
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
return false;
}

<Self as #trait_path>::property_can_revert(self, property)
}
}
};

property_can_revert_fn = Some(quote! {
#(#cfg_attrs)*
() => Some(#prv::callbacks::property_can_revert::<#class_name>),
});
}

"property_get_revert" => {
property_get_revert_impl = quote! {
#(#cfg_attrs)*
impl ::godot::obj::cap::GodotPropertyGetRevert for #class_name {
fn __godot_property_get_revert(&mut self, property: ::godot::builtin::StringName) -> Option<::godot::builtin::Variant> {
use ::godot::obj::UserClass as _;

#[cfg(before_api = "4.3")]
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
return None;
}

<Self as #trait_path>::property_get_revert(self, property)
}
}
};

property_get_revert_fn = Some(quote! {
#(#cfg_attrs)*
() => Some(#prv::callbacks::property_get_revert::<#class_name>),
});
}

// Other virtual methods, like ready, process etc.
_ => {
let method = util::reduce_to_signature(method);
@@ -863,6 +944,10 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
let on_notification_fn = convert_to_match_expression_or_none(on_notification_fn);
let get_property_fn = convert_to_match_expression_or_none(get_property_fn);
let set_property_fn = convert_to_match_expression_or_none(set_property_fn);
let get_property_list_fn = convert_to_match_expression_or_none(get_property_list_fn);
let free_property_list_fn = convert_to_match_expression_or_none(free_property_list_fn);
let property_can_revert_fn = convert_to_match_expression_or_none(property_can_revert_fn);
let property_get_revert_fn = convert_to_match_expression_or_none(property_get_revert_fn);

let result = quote! {
#original_impl
@@ -872,6 +957,9 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
#register_class_impl
#get_property_impl
#set_property_impl
#get_property_list_impl
#property_can_revert_impl
#property_get_revert_impl

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

@@ -901,6 +989,10 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
user_on_notification_fn: #on_notification_fn,
user_set_fn: #set_property_fn,
user_get_fn: #get_property_fn,
user_get_property_list_fn: #get_property_list_fn,
user_free_property_list_fn: #free_property_list_fn,
user_property_can_revert_fn: #property_can_revert_fn,
user_property_get_revert_fn: #property_get_revert_fn,
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
},
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
36 changes: 4 additions & 32 deletions itest/rust/src/builtin_tests/script/script_instance_tests.rs
Original file line number Diff line number Diff line change
@@ -48,44 +48,16 @@ impl TestScriptInstance {
Self {
script,
script_property_b: false,
prop_list: vec![PropertyInfo {
variant_type: VariantType::Int,
property_name: StringName::from("script_property_a"),
class_name: ClassName::from_ascii_cstr("\0".as_bytes()),
hint: PropertyHint::NONE,
hint_string: GString::new(),
usage: PropertyUsageFlags::NONE,
}],
prop_list: vec![PropertyInfo::new_var::<i64>("script_property_a")],

method_list: vec![MethodInfo {
id: 1,
method_name: StringName::from("script_method_a"),
class_name: ClassName::from_ascii_cstr("TestScript\0".as_bytes()),
return_type: PropertyInfo {
variant_type: VariantType::String,
class_name: ClassName::none(),
property_name: StringName::from(""),
hint: PropertyHint::NONE,
hint_string: GString::new(),
usage: PropertyUsageFlags::NONE,
},
return_type: PropertyInfo::new_return::<GString>(),
arguments: vec![
PropertyInfo {
variant_type: VariantType::String,
class_name: ClassName::none(),
property_name: StringName::from(""),
hint: PropertyHint::NONE,
hint_string: GString::new(),
usage: PropertyUsageFlags::NONE,
},
PropertyInfo {
variant_type: VariantType::Int,
class_name: ClassName::none(),
property_name: StringName::from(""),
hint: PropertyHint::NONE,
hint_string: GString::new(),
usage: PropertyUsageFlags::NONE,
},
PropertyInfo::new_arg::<GString>(""),
PropertyInfo::new_arg::<i64>(""),
],
default_arguments: vec![],
flags: MethodFlags::NORMAL,
23 changes: 23 additions & 0 deletions itest/rust/src/builtin_tests/string/gstring_test.rs
Original file line number Diff line number Diff line change
@@ -96,3 +96,26 @@ fn string_hash() {
.collect();
assert_eq!(set.len(), 5);
}

#[itest]
fn gstring_name_into_string_sys() {
const TARGET_STRINGS: &[&'static str] = &[
"property_number_one",
"another property here",
"wow properties",
"odakfhgjlk",
"more stuffsies",
];
let mut strings = Vec::new();

for i in 0..100 {
let string = TARGET_STRINGS[i % TARGET_STRINGS.len()];
strings.push(GString::from(string).into_string_sys());
}

for (i, string_sys) in strings.iter().enumerate() {
let target = TARGET_STRINGS[i % TARGET_STRINGS.len()];
let string = unsafe { GString::from_string_sys(*string_sys) };
assert_eq!(string.to_string().as_str(), target, "iteration: {i}",);
}
}
27 changes: 27 additions & 0 deletions itest/rust/src/builtin_tests/string/string_name_test.rs
Original file line number Diff line number Diff line change
@@ -141,3 +141,30 @@ fn string_name_from_latin1_with_nul() {
assert_eq!(a, b);
}
}

#[itest]
fn string_name_into_string_sys() {
const TARGET_STRING_NAMES: &[&'static str] = &[
"property_number_one",
"another property here",
"wow properties",
"odakfhgjlk",
"more stuffsies",
];
let mut string_names = Vec::new();

for i in 0..100 {
let string_name = TARGET_STRING_NAMES[i % TARGET_STRING_NAMES.len()];
string_names.push(StringName::from(string_name).into_string_sys());
}

for (i, string_name_sys) in string_names.iter().enumerate() {
let target_name = TARGET_STRING_NAMES[i % TARGET_STRING_NAMES.len()];
let string_name = unsafe { StringName::from_string_sys(*string_name_sys) };
assert_eq!(
string_name.to_string().as_str(),
target_name,
"iteration: {i}",
);
}
}
88 changes: 88 additions & 0 deletions itest/rust/src/object_tests/dyn_property_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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/.
*/

// Testing dynamic properties exposed mainly through `get_property_list` and other helper methods.

use crate::framework::itest;

use godot::builtin::meta::{PropertyInfo, ToGodot};
use godot::builtin::{GString, StringName, VariantArray};
use godot::engine::{INode, Node, Object};
use godot::obj::{Gd, Inherits, NewAlloc};
use godot::register::{godot_api, GodotClass};

#[derive(GodotClass)]
#[class(init, base = Node)]
struct PropertyListTest {
toggle_props: bool,
}

#[godot_api]
impl INode for PropertyListTest {
fn get_property_list(&self) -> Vec<PropertyInfo> {
let mut properties = vec![
PropertyInfo::new_var::<i64>("some_i64_property"),
PropertyInfo::new_var::<GString>("some_gstring_property"),
PropertyInfo::new_var::<VariantArray>("some_variantarray_property"),
];

if self.toggle_props {
properties.push(PropertyInfo::new_var::<Option<Gd<Node>>>(
"some_toggled_property",
));
}

properties
}
}

fn has_property<T: Inherits<Object>, S: Into<StringName>>(gd: &Gd<T>, property: S) -> bool {
let gd = gd.clone().upcast::<Object>();
let property = property.into();
let property_list = gd.get_property_list();

for prop in property_list.iter_shared() {
if prop.get("name") == Some(property.to_variant()) {
return true;
}
}

false
}

#[itest]
fn property_list_has_property() {
let mut property_list_test = PropertyListTest::new_alloc();

assert!(has_property(&property_list_test, "some_i64_property"));
assert!(has_property(&property_list_test, "some_gstring_property"));
assert!(has_property(
&property_list_test,
"some_variantarray_property"
));
assert!(!has_property(&property_list_test, "some_toggled_property"));
assert!(!has_property(
&property_list_test,
"some_undefined_property"
));

property_list_test.bind_mut().toggle_props = true;

assert!(has_property(&property_list_test, "some_i64_property"));
assert!(has_property(&property_list_test, "some_gstring_property"));
assert!(has_property(
&property_list_test,
"some_variantarray_property"
));
assert!(has_property(&property_list_test, "some_toggled_property"));
assert!(!has_property(
&property_list_test,
"some_undefined_property"
));

property_list_test.free();
}
1 change: 1 addition & 0 deletions itest/rust/src/object_tests/mod.rs
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@

mod base_test;
mod class_rename_test;
mod dyn_property_test;
mod object_swap_test;
mod object_test;
mod onready_test;