Skip to content

Commit fac4c3f

Browse files
committed
Add property_get_revert
Add `property_can_revert`
1 parent 350aba6 commit fac4c3f

File tree

9 files changed

+236
-1
lines changed

9 files changed

+236
-1
lines changed

godot-codegen/src/generator/virtual_traits.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,23 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
115115
fn get_property_list(&mut self) -> Vec<crate::builtin::meta::PropertyInfo> {
116116
unimplemented!()
117117
}
118+
119+
/// Called by Godot to tell if a property has a custom revert or not.
120+
///
121+
/// Return `None` for no custom revert, and return `Some(value)` to specify the custom revert.
122+
///
123+
/// This is a combination of Godot's [`Object::_property_get_revert`] and [`Object::_property_can_revert`]. This means that this
124+
/// function will usually be called twice by Godot to find the revert.
125+
///
126+
/// Note that this should be a _pure_ function. That is, it should always return the same value for a property as long as `self`
127+
/// remains unchanged. Otherwise this may lead to unexpected (safe) behavior.
128+
///
129+
/// [`Object::_property_get_revert`]: https://docs.godotengine.org/en/latest/classes/class_object.html#class-object-private-method-property-get-revert
130+
/// [`Object::_property_can_revert`]: https://docs.godotengine.org/en/latest/classes/class_object.html#class-object-private-method-property-can-revert
131+
#[doc(alias = "property_can_revert")]
132+
fn property_get_revert(&self, property: StringName) -> Option<Variant> {
133+
unimplemented!()
134+
}
118135
}
119136
}
120137

godot-core/src/builtin/meta/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ impl PropertyInfo {
324324
///
325325
/// Creating an `@export_range` property.
326326
///
327-
328327
// TODO: Make this nicer to use.
329328
/// ```no_run
330329
/// # use crate::property::export_info_function;

godot-core/src/builtin/string/gstring.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ impl GString {
136136
///
137137
/// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
138138
pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringPtr {
139+
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
140+
139141
let leaked = Box::into_raw(Box::new(self));
140142
leaked.cast()
141143
}
@@ -148,6 +150,8 @@ impl GString {
148150
/// * Must not be called more than once on the same pointer.
149151
#[deny(unsafe_op_in_unsafe_fn)]
150152
pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringPtr) -> Self {
153+
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);
154+
151155
let ptr = ptr.cast::<Self>();
152156
let boxed = unsafe { Box::from_raw(ptr) };
153157
*boxed

godot-core/src/builtin/string/string_name.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ impl StringName {
131131
/// * Must not be called more than once on the same pointer.
132132
#[deny(unsafe_op_in_unsafe_fn)]
133133
pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringNamePtr) -> Self {
134+
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
135+
134136
let ptr = ptr.cast::<Self>();
135137
let boxed = unsafe { Box::from_raw(ptr) };
136138
*boxed

godot-core/src/obj/traits.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,12 @@ pub mod cap {
510510
fn __godot_get_property_list(&mut self) -> Vec<crate::builtin::meta::PropertyInfo>;
511511
}
512512

513+
#[doc(hidden)]
514+
pub trait GodotPropertyGetRevert: GodotClass {
515+
#[doc(hidden)]
516+
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
517+
}
518+
513519
/// Auto-implemented for `#[godot_api] impl MyClass` blocks
514520
pub trait ImplementsGodotApi: GodotClass {
515521
#[doc(hidden)]

godot-core/src/registry/callbacks.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,54 @@ pub unsafe extern "C" fn free_property_list<T: cap::GodotGetPropertyList>(
248248
}
249249
}
250250
}
251+
252+
/// # Safety
253+
///
254+
/// * `instance` must be a valid `T` instance pointer for the duration of this function call.
255+
/// * `property_name` must be a valid `StringName` pointer for the duration of this function call.
256+
#[deny(unsafe_op_in_unsafe_fn)]
257+
unsafe fn raw_property_get_revert<T: cap::GodotPropertyGetRevert>(
258+
instance: sys::GDExtensionClassInstancePtr,
259+
property_name: sys::GDExtensionConstStringNamePtr,
260+
) -> Option<Variant> {
261+
// SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call.
262+
let storage = unsafe { as_storage::<T>(instance) };
263+
let instance = storage.get();
264+
265+
// SAFETY: `property_name` is a valid `StringName` pointer for the duration of this function call.
266+
let property = unsafe { StringName::borrow_string_sys(property_name) };
267+
T::__godot_property_get_revert(&*instance, property.clone())
268+
}
269+
270+
#[deny(unsafe_op_in_unsafe_fn)]
271+
pub unsafe extern "C" fn property_can_revert<T: cap::GodotPropertyGetRevert>(
272+
instance: sys::GDExtensionClassInstancePtr,
273+
property_name: sys::GDExtensionConstStringNamePtr,
274+
) -> sys::GDExtensionBool {
275+
// SAFETY: Godot provides us with a valid `T` instance pointer and `StringName` pointer for the duration of this call.
276+
let revert = unsafe { raw_property_get_revert::<T>(instance, property_name) };
277+
278+
revert.is_some() as sys::GDExtensionBool
279+
}
280+
281+
#[deny(unsafe_op_in_unsafe_fn)]
282+
pub unsafe extern "C" fn property_get_revert<T: cap::GodotPropertyGetRevert>(
283+
instance: sys::GDExtensionClassInstancePtr,
284+
property_name: sys::GDExtensionConstStringNamePtr,
285+
ret: sys::GDExtensionVariantPtr,
286+
) -> sys::GDExtensionBool {
287+
// SAFETY: Godot provides us with a valid `T` instance pointer and `StringName` pointer for the duration of this call.
288+
let Some(revert) = (unsafe { raw_property_get_revert::<T>(instance, property_name) }) else {
289+
return false as sys::GDExtensionBool;
290+
};
291+
292+
// SAFETY: Godot provides us with a valid `Variant` pointer.
293+
unsafe {
294+
revert.move_into_var_ptr(ret);
295+
}
296+
297+
true as sys::GDExtensionBool
298+
}
251299
// ----------------------------------------------------------------------------------------------------------------------------------------------
252300
// Safe, higher-level methods
253301

godot-core/src/registry/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ pub enum PluginItem {
205205
p_count: u32,
206206
),
207207
>,
208+
209+
user_property_can_revert_fn: Option<
210+
unsafe extern "C" fn(
211+
p_instance: sys::GDExtensionClassInstancePtr,
212+
p_name: sys::GDExtensionConstStringNamePtr,
213+
) -> sys::GDExtensionBool,
214+
>,
215+
216+
user_property_get_revert_fn: Option<
217+
unsafe extern "C" fn(
218+
p_instance: sys::GDExtensionClassInstancePtr,
219+
p_name: sys::GDExtensionConstStringNamePtr,
220+
r_ret: sys::GDExtensionVariantPtr,
221+
) -> sys::GDExtensionBool,
222+
>,
208223
},
209224
}
210225

@@ -478,6 +493,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
478493
get_virtual_fn,
479494
user_get_property_list_fn,
480495
user_free_property_list_fn,
496+
user_property_can_revert_fn,
497+
user_property_get_revert_fn,
481498
} => {
482499
c.user_register_fn = user_register_fn;
483500

@@ -500,6 +517,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
500517
c.godot_params.get_func = user_get_fn;
501518
c.godot_params.get_property_list_func = user_get_property_list_fn;
502519
c.godot_params.free_property_list_func = user_free_property_list_fn;
520+
c.godot_params.property_can_revert_func = user_property_can_revert_fn;
521+
c.godot_params.property_get_revert_func = user_property_get_revert_fn;
503522
c.user_virtual_fn = Some(get_virtual_fn);
504523
}
505524
}

godot-macros/src/class/data_models/interface_trait_impl.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
2222
let mut on_notification_impl = TokenStream::new();
2323
let mut get_property_impl = TokenStream::new();
2424
let mut set_property_impl = TokenStream::new();
25+
let mut get_property_list_impl = TokenStream::new();
26+
let mut property_get_revert_impl = TokenStream::new();
2527

2628
let mut register_fn = None;
2729
let mut create_fn = None;
@@ -30,6 +32,10 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
3032
let mut on_notification_fn = None;
3133
let mut get_property_fn = None;
3234
let mut set_property_fn = None;
35+
let mut get_property_list_fn = None;
36+
let mut free_property_list_fn = None;
37+
let mut property_get_revert_fn = None;
38+
let mut property_can_revert_fn = None;
3339

3440
let mut virtual_methods = vec![];
3541
let mut virtual_method_cfg_attrs = vec![];
@@ -204,6 +210,76 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
204210
});
205211
}
206212

213+
#[cfg(before_api = "4.3")]
214+
"get_property_list" => {
215+
get_property_list_impl = quote! {
216+
#(#cfg_attrs)*
217+
compile_error!("`get_property_list` is only supported for Godot versions of at least 4.3");
218+
};
219+
220+
// Set these variables otherwise rust complains that these variables arent changed in Godot < 4.3.
221+
get_property_list_fn = None;
222+
free_property_list_fn = None;
223+
}
224+
225+
#[cfg(since_api = "4.3")]
226+
"get_property_list" => {
227+
get_property_list_impl = quote! {
228+
#(#cfg_attrs)*
229+
impl ::godot::obj::cap::GodotGetPropertyList for #class_name {
230+
fn __godot_get_property_list(&mut self) -> Vec<::godot::builtin::meta::PropertyInfo> {
231+
// Only supported in godot api > 4.3. If support is added for earlier versions this is still needed.
232+
//
233+
// use ::godot::obj::UserClass as _;
234+
//
235+
// #[cfg(before_api = "4.3")]
236+
// if ::godot::private::is_class_inactive(Self::__config().is_tool) {
237+
// return false;
238+
// }
239+
240+
<Self as #trait_path>::get_property_list(self)
241+
}
242+
}
243+
};
244+
245+
get_property_list_fn = Some(quote! {
246+
#(#cfg_attrs)*
247+
() => Some(#prv::callbacks::get_property_list::<#class_name>),
248+
});
249+
free_property_list_fn = Some(quote! {
250+
#(#cfg_attrs)*
251+
() => Some(#prv::callbacks::free_property_list::<#class_name>),
252+
});
253+
}
254+
255+
"property_get_revert" => {
256+
property_get_revert_impl = quote! {
257+
#(#cfg_attrs)*
258+
impl ::godot::obj::cap::GodotPropertyGetRevert for #class_name {
259+
fn __godot_property_get_revert(&self, property: StringName) -> Option<::godot::builtin::Variant> {
260+
use ::godot::obj::UserClass as _;
261+
262+
#[cfg(before_api = "4.3")]
263+
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
264+
return None;
265+
}
266+
267+
<Self as #trait_path>::property_get_revert(self, property)
268+
}
269+
}
270+
};
271+
272+
property_get_revert_fn = Some(quote! {
273+
#(#cfg_attrs)*
274+
() => Some(#prv::callbacks::property_get_revert::<#class_name>),
275+
});
276+
277+
property_can_revert_fn = Some(quote! {
278+
#(#cfg_attrs)*
279+
() => Some(#prv::callbacks::property_can_revert::<#class_name>),
280+
});
281+
}
282+
207283
// Other virtual methods, like ready, process etc.
208284
_ => {
209285
let method = util::reduce_to_signature(method);
@@ -274,6 +350,10 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
274350
let on_notification_fn = convert_to_match_expression_or_none(on_notification_fn);
275351
let get_property_fn = convert_to_match_expression_or_none(get_property_fn);
276352
let set_property_fn = convert_to_match_expression_or_none(set_property_fn);
353+
let get_property_list_fn = convert_to_match_expression_or_none(get_property_list_fn);
354+
let free_property_list_fn = convert_to_match_expression_or_none(free_property_list_fn);
355+
let property_get_revert_fn = convert_to_match_expression_or_none(property_get_revert_fn);
356+
let property_can_revert_fn = convert_to_match_expression_or_none(property_can_revert_fn);
277357

278358
let result = quote! {
279359
#original_impl
@@ -283,6 +363,8 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
283363
#register_class_impl
284364
#get_property_impl
285365
#set_property_impl
366+
#get_property_list_impl
367+
#property_get_revert_impl
286368

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

@@ -312,6 +394,10 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
312394
user_on_notification_fn: #on_notification_fn,
313395
user_set_fn: #set_property_fn,
314396
user_get_fn: #get_property_fn,
397+
user_get_property_list_fn: #get_property_list_fn,
398+
user_free_property_list_fn: #free_property_list_fn,
399+
user_property_get_revert_fn: #property_get_revert_fn,
400+
user_property_can_revert_fn: #property_can_revert_fn,
315401
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
316402
},
317403
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,

itest/rust/src/object_tests/virtual_methods_test.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,34 @@ impl IRefCounted for SetTest {
282282

283283
// ----------------------------------------------------------------------------------------------------------------------------------------------
284284

285+
#[derive(GodotClass)]
286+
#[class(init)]
287+
struct RevertTest {}
288+
289+
#[godot_api]
290+
impl IRefCounted for RevertTest {
291+
fn property_get_revert(&self, property: StringName) -> Option<Variant> {
292+
use std::sync::atomic::AtomicUsize;
293+
294+
static INC: AtomicUsize = AtomicUsize::new(0);
295+
296+
match String::from(property).as_str() {
297+
"property_not_revert" => None,
298+
"property_do_revert" => Some(GString::from("hello!").to_variant()),
299+
"property_changes" => {
300+
if INC.fetch_add(1, std::sync::atomic::Ordering::AcqRel) % 2 == 0 {
301+
None
302+
} else {
303+
Some(true.to_variant())
304+
}
305+
}
306+
_ => None,
307+
}
308+
}
309+
}
310+
311+
// ----------------------------------------------------------------------------------------------------------------------------------------------
312+
285313
#[itest]
286314
fn test_to_string() {
287315
let _obj = VirtualMethodTest::new_gd();
@@ -563,6 +591,32 @@ fn test_set_sets_correct() {
563591
assert_eq!(obj.bind().settable, 500);
564592
}
565593

594+
#[itest]
595+
fn test_revert() {
596+
let revert = RevertTest::new_gd();
597+
598+
let not_revert = StringName::from("property_not_revert");
599+
let do_revert = StringName::from("property_do_revert");
600+
let changes = StringName::from("property_changes");
601+
602+
assert!(!revert.property_can_revert(not_revert.clone()));
603+
assert_eq!(revert.property_get_revert(not_revert), Variant::nil());
604+
assert!(revert.property_can_revert(do_revert.clone()));
605+
assert_eq!(
606+
revert.property_get_revert(do_revert),
607+
GString::from("hello!").to_variant()
608+
);
609+
610+
assert!(!revert.property_can_revert(changes.clone()));
611+
assert!(revert.property_can_revert(changes.clone()));
612+
613+
assert_eq!(revert.property_get_revert(changes.clone()), Variant::nil());
614+
assert_eq!(
615+
revert.property_get_revert(changes.clone()),
616+
true.to_variant()
617+
);
618+
}
619+
566620
// Used in `test_collision_object_2d_input_event` in `SpecialTests.gd`.
567621
#[derive(GodotClass)]
568622
#[class(init, base = RigidBody2D)]

0 commit comments

Comments
 (0)