Skip to content

Commit 60a42ae

Browse files
authored
Merge pull request #567 from lilizoey/feature/get-set-virtual
Add `get` and `set` as virtual methods
2 parents 6143ed4 + a7a09a8 commit 60a42ae

File tree

6 files changed

+353
-0
lines changed

6 files changed

+353
-0
lines changed

godot-codegen/src/class_generator.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,28 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
16461646
fn on_notification(&mut self, what: #notification_enum_name) {
16471647
unimplemented!()
16481648
}
1649+
1650+
/// Called whenever [`get()`](crate::engine::Object::get) is called or Godot gets the value of a property.
1651+
///
1652+
/// Should return the given `property`'s value as `Some(value)`, or `None` if the property should be handled normally.
1653+
///
1654+
/// See also in Godot docs:
1655+
/// * [`Object::_get`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-get).
1656+
fn get(&self, property: StringName) -> Option<Variant> {
1657+
unimplemented!()
1658+
}
1659+
1660+
/// Called whenever Godot [`set()`](crate::engine::Object::set) is called or Godot sets the value of a property.
1661+
///
1662+
/// Should set `property` to the given `value` and return `true`, or return `false` to indicate the `property`
1663+
/// should be handled normally.
1664+
///
1665+
/// See also in Godot docs:
1666+
/// * [`Object::_set`](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-set).
1667+
fn set(&mut self, property: StringName, value: Variant) -> bool {
1668+
unimplemented!()
1669+
}
1670+
16491671
}
16501672
}
16511673

godot-core/src/obj/traits.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ where
416416
/// Capability traits, providing dedicated functionalities for Godot classes
417417
pub mod cap {
418418
use super::*;
419+
use crate::builtin::{StringName, Variant};
419420
use crate::obj::{Base, Bounds, Gd};
420421

421422
/// Trait for all classes that are default-constructible from the Godot engine.
@@ -483,6 +484,18 @@ pub mod cap {
483484
fn __godot_register_class(builder: &mut ClassBuilder<Self>);
484485
}
485486

487+
#[doc(hidden)]
488+
pub trait GodotGet: GodotClass {
489+
#[doc(hidden)]
490+
fn __godot_get(&self, property: StringName) -> Option<Variant>;
491+
}
492+
493+
#[doc(hidden)]
494+
pub trait GodotSet: GodotClass {
495+
#[doc(hidden)]
496+
fn __godot_set(&mut self, property: StringName, value: Variant) -> bool;
497+
}
498+
486499
/// Auto-implemented for `#[godot_api] impl MyClass` blocks
487500
pub trait ImplementsGodotApi: GodotClass {
488501
#[doc(hidden)]

godot-core/src/registry.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@ pub enum PluginComponent {
148148
),
149149
>,
150150

151+
user_set_fn: Option<
152+
unsafe extern "C" fn(
153+
p_instance: sys::GDExtensionClassInstancePtr,
154+
p_name: sys::GDExtensionConstStringNamePtr,
155+
p_value: sys::GDExtensionConstVariantPtr,
156+
) -> sys::GDExtensionBool,
157+
>,
158+
159+
user_get_fn: Option<
160+
unsafe extern "C" fn(
161+
p_instance: sys::GDExtensionClassInstancePtr,
162+
p_name: sys::GDExtensionConstStringNamePtr,
163+
r_ret: sys::GDExtensionVariantPtr,
164+
) -> sys::GDExtensionBool,
165+
>,
166+
151167
/// Callback for other virtuals.
152168
get_virtual_fn: unsafe extern "C" fn(
153169
p_userdata: *mut std::os::raw::c_void,
@@ -373,6 +389,8 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
373389
user_recreate_fn,
374390
user_to_string_fn,
375391
user_on_notification_fn,
392+
user_set_fn,
393+
user_get_fn,
376394
get_virtual_fn,
377395
} => {
378396
c.user_register_fn = user_register_fn;
@@ -390,6 +408,8 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
390408

391409
c.godot_params.to_string_func = user_to_string_fn;
392410
c.godot_params.notification_func = user_on_notification_fn;
411+
c.godot_params.set_func = user_set_fn;
412+
c.godot_params.get_func = user_get_fn;
393413
c.user_virtual_fn = Some(get_virtual_fn);
394414
}
395415
#[cfg(since_api = "4.1")]
@@ -510,6 +530,7 @@ fn unregister_class_raw(class_name: ClassName) {
510530
pub mod callbacks {
511531
use super::*;
512532
use crate::builder::ClassBuilder;
533+
use crate::builtin::Variant;
513534
use crate::obj::Base;
514535
use crate::storage::{Storage, StorageRefCounted};
515536

@@ -657,6 +678,43 @@ pub mod callbacks {
657678
T::__godot_notification(&mut *instance, what);
658679
}
659680

681+
pub unsafe extern "C" fn get<T: cap::GodotGet>(
682+
instance: sys::GDExtensionClassInstancePtr,
683+
name: sys::GDExtensionConstStringNamePtr,
684+
ret: sys::GDExtensionVariantPtr,
685+
) -> sys::GDExtensionBool {
686+
let storage = as_storage::<T>(instance);
687+
let instance = storage.get();
688+
let property = StringName::from_string_sys(sys::force_mut_ptr(name));
689+
690+
std::mem::forget(property.clone());
691+
692+
match T::__godot_get(&*instance, property) {
693+
Some(value) => {
694+
value.move_var_ptr(ret);
695+
true as sys::GDExtensionBool
696+
}
697+
None => false as sys::GDExtensionBool,
698+
}
699+
}
700+
701+
pub unsafe extern "C" fn set<T: cap::GodotSet>(
702+
instance: sys::GDExtensionClassInstancePtr,
703+
name: sys::GDExtensionConstStringNamePtr,
704+
value: sys::GDExtensionConstVariantPtr,
705+
) -> sys::GDExtensionBool {
706+
let storage = as_storage::<T>(instance);
707+
let mut instance = storage.get_mut();
708+
709+
let property = StringName::from_string_sys(sys::force_mut_ptr(name));
710+
let value = Variant::from_var_sys(sys::force_mut_ptr(value));
711+
712+
std::mem::forget(property.clone());
713+
std::mem::forget(value.clone());
714+
715+
T::__godot_set(&mut *instance, property, value) as sys::GDExtensionBool
716+
}
717+
660718
pub unsafe extern "C" fn reference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
661719
let storage = as_storage::<T>(instance);
662720
storage.on_inc_ref();

godot-macros/src/class/godot_api.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,12 +459,16 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
459459
let mut to_string_impl = TokenStream::new();
460460
let mut register_class_impl = TokenStream::new();
461461
let mut on_notification_impl = TokenStream::new();
462+
let mut get_impl = TokenStream::new();
463+
let mut set_impl = TokenStream::new();
462464

463465
let mut register_fn = None;
464466
let mut create_fn = None;
465467
let mut recreate_fn = None;
466468
let mut to_string_fn = None;
467469
let mut on_notification_fn = None;
470+
let mut get_fn = None;
471+
let mut set_fn = None;
468472

469473
let mut virtual_methods = vec![];
470474
let mut virtual_method_cfg_attrs = vec![];
@@ -591,6 +595,54 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
591595
});
592596
}
593597

598+
"get" => {
599+
get_impl = quote! {
600+
#get_impl
601+
602+
#(#cfg_attrs)*
603+
impl ::godot::obj::cap::GodotGet for #class_name {
604+
fn __godot_get(&self, property: ::godot::builtin::StringName) -> Option<::godot::builtin::Variant> {
605+
use ::godot::obj::UserClass as _;
606+
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
607+
return None;
608+
}
609+
610+
<Self as #trait_name>::get(self, property)
611+
}
612+
}
613+
};
614+
615+
get_fn = Some(quote! {
616+
#get_fn
617+
#(#cfg_attrs)*
618+
() => Some(#prv::callbacks::get::<#class_name>),
619+
});
620+
}
621+
622+
"set" => {
623+
set_impl = quote! {
624+
#set_impl
625+
626+
#(#cfg_attrs)*
627+
impl ::godot::obj::cap::GodotSet for #class_name {
628+
fn __godot_set(&mut self, property: ::godot::builtin::StringName, value: ::godot::builtin::Variant) -> bool {
629+
use ::godot::obj::UserClass as _;
630+
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
631+
return false;
632+
}
633+
634+
<Self as #trait_name>::set(self, property, value)
635+
}
636+
}
637+
};
638+
639+
set_fn = Some(quote! {
640+
#set_fn
641+
#(#cfg_attrs)*
642+
() => Some(#prv::callbacks::set::<#class_name>),
643+
});
644+
}
645+
594646
// Other virtual methods, like ready, process etc.
595647
_ => {
596648
let method = util::reduce_to_signature(method);
@@ -659,13 +711,17 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
659711
let recreate_fn = convert_to_match_expression_or_none(recreate_fn);
660712
let to_string_fn = convert_to_match_expression_or_none(to_string_fn);
661713
let on_notification_fn = convert_to_match_expression_or_none(on_notification_fn);
714+
let get_fn = convert_to_match_expression_or_none(get_fn);
715+
let set_fn = convert_to_match_expression_or_none(set_fn);
662716

663717
let result = quote! {
664718
#original_impl
665719
#godot_init_impl
666720
#to_string_impl
667721
#on_notification_impl
668722
#register_class_impl
723+
#get_impl
724+
#set_impl
669725

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

@@ -693,6 +749,8 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
693749
user_recreate_fn: #recreate_fn,
694750
user_to_string_fn: #to_string_fn,
695751
user_on_notification_fn: #on_notification_fn,
752+
user_set_fn: #set_fn,
753+
user_get_fn: #get_fn,
696754
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
697755
},
698756
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,

itest/godot/ManualFfiTests.gd

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,26 @@ func test_callable_refcount():
345345
var method := Callable(self, "sample_func")
346346
assert(method.is_valid())
347347
test_obj.free()
348+
349+
func test_get_set():
350+
var obj: GetSetTest = GetSetTest.new()
351+
assert(not obj.is_get_called())
352+
assert(not obj.is_set_called())
353+
354+
assert_eq(obj.always_get_100, 100)
355+
assert(obj.is_get_called())
356+
assert(not obj.is_set_called())
357+
obj.unset_get_called()
358+
359+
obj.always_get_100 = 10
360+
assert_eq(obj.always_get_100, 100)
361+
assert_eq(obj.get_real_always_get_100(), 10)
362+
assert(obj.is_set_called())
363+
obj.unset_get_called()
364+
obj.unset_set_called()
365+
366+
obj.set_get = 1000
367+
assert_eq(obj.set_get, 1000)
368+
assert(obj.is_set_called())
369+
assert(obj.is_get_called())
370+

0 commit comments

Comments
 (0)